diff --git a/.gitignore b/.gitignore
index 6ef1d5bc..9c901e75 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,4 +38,6 @@ get-pip.py
venv
test
+test_games/
+data/sprites/official/selan.1.zspr
*.zspr
diff --git a/Adjuster.py b/Adjuster.py
index a6e964a8..9e003b3f 100755
--- a/Adjuster.py
+++ b/Adjuster.py
@@ -37,6 +37,7 @@ def main():
parser.add_argument('--uw_palettes', default='default', choices=['default', 'random', 'blackout'])
parser.add_argument('--reduce_flashing', help='Reduce some in-game flashing.', action='store_true')
parser.add_argument('--shuffle_sfx', help='Shuffles sound sfx', action='store_true')
+ parser.add_argument('--msu_resume', help='Enable MSU resume', action='store_true')
parser.add_argument('--sprite', help='''\
Path to a sprite sheet to use for Link. Needs to be in
binary format and have a length of 0x7000 (28672) bytes,
diff --git a/AdjusterMain.py b/AdjusterMain.py
index 7d7e2f6e..c6f19680 100644
--- a/AdjusterMain.py
+++ b/AdjusterMain.py
@@ -2,8 +2,15 @@ import os
import time
import logging
+try:
+ import bps.apply
+ import bps.io
+except ImportError:
+ raise Exception('Could not load BPS module')
+
from Utils import output_path
from Rom import LocalRom, apply_rom_settings
+from source.tools.BPS import bps_read_vlv
def adjust(args):
@@ -25,7 +32,8 @@ def adjust(args):
args.sprite = None
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic,
- args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx)
+ args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx,
+ args.msu_resume)
output_path.cached_path = args.outputpath
rom.write_to_file(output_path('%s.sfc' % outfilebase))
@@ -34,3 +42,38 @@ def adjust(args):
logger.debug('Total Time: %s', time.process_time() - start)
return args
+
+
+def patch(args):
+ start = time.process_time()
+ logger = logging.getLogger('')
+ logger.info('Patching ROM.')
+
+ outfile_base = os.path.basename(args.patch)[:-4]
+
+ rom = LocalRom(args.baserom, False)
+ if os.path.isfile(args.baserom):
+ rom.verify_base_rom()
+ orig_buffer = rom.buffer.copy()
+ with open(args.patch, 'rb') as stream:
+ stream.seek(4) # skip BPS1
+ bps_read_vlv(stream) # skip source size
+ target_length = bps_read_vlv(stream)
+ rom.buffer.extend(bytearray([0x00] * (target_length - len(rom.buffer))))
+ stream.seek(0)
+ bps.apply.apply_to_bytearrays(bps.io.read_bps(stream), orig_buffer, rom.buffer)
+
+ if not hasattr(args, "sprite"):
+ args.sprite = None
+
+ apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic,
+ args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx,
+ args.msu_resume)
+
+ output_path.cached_path = args.outputpath
+ rom.write_to_file(output_path('%s.sfc' % outfile_base))
+
+ logger.info('Done. Enjoy.')
+ logger.debug('Total Time: %s', time.process_time() - start)
+
+ return args
diff --git a/BaseClasses.py b/BaseClasses.py
index 289b681b..154eacec 100644
--- a/BaseClasses.py
+++ b/BaseClasses.py
@@ -15,6 +15,8 @@ from source.classes.BabelFish import BabelFish
from Utils import int16_as_bytes
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
from RoomData import Room
+from source.dungeon.RoomObject import RoomObject
+
class World(object):
@@ -52,7 +54,7 @@ class World(object):
self._entrance_cache = {}
self._location_cache = {}
self.required_locations = []
- self.shuffle_bonk_prizes = False
+ self.shuffle_bonk_drops = {}
self.light_world_light_cone = False
self.dark_world_light_cone = False
self.clock_mode = 'none'
@@ -60,8 +62,9 @@ class World(object):
self.aga_randomness = True
self.lock_aga_door_in_escape = False
self.save_and_quit_from_boss = True
+ self.override_bomb_check = False
+ self.is_copied_world = False
self.accessibility = accessibility.copy()
- self.initial_overworld_flags = {}
self.fix_skullwoods_exit = {}
self.fix_palaceofdarkness_exit = {}
self.fix_trock_exit = {}
@@ -98,6 +101,7 @@ class World(object):
self._portal_cache = {}
self.sanc_portal = {}
self.fish = BabelFish()
+ self.pot_contents = {}
for player in range(1, players + 1):
# If World State is Retro, set to Open and set Retro flag
@@ -119,8 +123,7 @@ class World(object):
set_player_attr('ganon_at_pyramid', True)
set_player_attr('ganonstower_vanilla', True)
set_player_attr('sewer_light_cone', self.mode[player] == 'standard')
- set_player_attr('initial_overworld_flags', [0] * 0x80)
- set_player_attr('fix_trock_doors', self.shuffle[player] != 'vanilla' or ((self.mode[player] == 'inverted') != 0x05 in self.owswaps[player][0]))
+ set_player_attr('fix_trock_doors', self.shuffle[player] != 'vanilla' or self.is_tile_swapped(0x05, player))
set_player_attr('fix_skullwoods_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'] or self.doorShuffle[player] not in ['vanilla'])
set_player_attr('fix_palaceofdarkness_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
set_player_attr('fix_trock_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
@@ -133,6 +136,7 @@ class World(object):
set_player_attr('compassshuffle', False)
set_player_attr('keyshuffle', False)
set_player_attr('bigkeyshuffle', False)
+ set_player_attr('restrict_boss_items', 'none')
set_player_attr('bombbag', False)
set_player_attr('difficulty_requirements', None)
set_player_attr('boss_shuffle', 'none')
@@ -147,16 +151,18 @@ class World(object):
set_player_attr('crystals_gt_orig', {})
set_player_attr('ganon_item', 'default')
set_player_attr('ganon_item_orig', 'default')
- set_player_attr('open_pyramid', False)
+ set_player_attr('open_pyramid', 'auto')
set_player_attr('treasure_hunt_icon', 'Triforce Piece')
set_player_attr('treasure_hunt_count', 0)
set_player_attr('treasure_hunt_total', 0)
set_player_attr('potshuffle', False)
set_player_attr('pot_contents', None)
set_player_attr('pseudoboots', False)
+ set_player_attr('collection_rate', False)
+ set_player_attr('colorizepots', False)
+ set_player_attr('pot_pool', {})
set_player_attr('shopsanity', False)
- set_player_attr('keydropshuffle', False)
set_player_attr('mixed_travel', 'prevent')
set_player_attr('standardize_palettes', 'standardize')
set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False})
@@ -258,6 +264,11 @@ class World(object):
return r_location
raise RuntimeError('No such location %s for player %d' % (location, player))
+ def get_location_unsafe(self, location, player):
+ if (location, player) in self._location_cache:
+ return self._location_cache[(location, player)]
+ return None
+
def get_dungeon(self, dungeonname, player):
if isinstance(dungeonname, Dungeon):
return dungeonname
@@ -307,11 +318,24 @@ class World(object):
return (self.mode[player] == 'inverted') != (owid in self.owswaps[player][0] and self.owMixed[player])
def is_atgt_swapped(self, player):
- return (0x03 in self.owswaps[player][0]) == (0x1b in self.owswaps[player][0]) == (self.mode[player] != 'inverted')
+ return self.is_tile_swapped(0x03, player) and self.is_tile_swapped(0x1b, player)
def is_bombshop_start(self, player):
return self.is_tile_swapped(0x2c, player) and (self.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not self.shufflelinks[player])
+ def is_pyramid_open(self, player):
+ if self.open_pyramid[player] == 'yes':
+ return True
+ elif self.open_pyramid[player] == 'no':
+ return False
+ else:
+ if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
+ return False
+ elif self.goal[player] in ['crystals', 'trinity']:
+ return True
+ else:
+ return False
+
def check_for_door(self, doorname, player):
if isinstance(doorname, Door):
return doorname
@@ -417,7 +441,7 @@ class World(object):
elif item.name.startswith('Bottle'):
if ret.bottle_count(item.player) < self.difficulty_requirements[item.player].progressive_bottle_limit:
ret.prog_items[item.name, item.player] += 1
- elif item.advancement or item.smallkey or item.bigkey:
+ elif item.advancement or item.smallkey or item.bigkey or item.compass or item.map:
ret.prog_items[item.name, item.player] += 1
if item.name.endswith(' Cane'):
if ret.world.swords[item.player] == 'byrna' and not ret.has('Cane of Byrna', item.player):
@@ -437,6 +461,8 @@ class World(object):
key_list += [dungeon.big_key.name]
if len(dungeon.small_keys) > 0:
key_list += [x.name for x in dungeon.small_keys]
+ # map/compass may be required now
+ key_list += [x.name for x in dungeon.dungeon_items]
from Items import ItemFactory
for item in ItemFactory(key_list, p):
soft_collect(item)
@@ -467,6 +493,8 @@ class World(object):
location.item = item
item.location = location
item.world = self
+ if location.player != item.player and location.type == LocationType.Pot:
+ self.pot_contents[location.player].multiworld_count += 1
if collect:
self.state.collect(item, location.event, location)
@@ -948,7 +976,7 @@ class CollectionState(object):
reduced = Counter()
for item, cnt in self.prog_items.items():
item_name, item_player = item
- if item_player == player and self.check_if_progressive(item_name):
+ if item_player == player and self.check_if_progressive(item_name, player):
if item_name.startswith('Bottle'): # I think magic requirements can require multiple bottles
bottle_count += cnt
elif item_name in ['Boss Heart Container', 'Sanctuary Heart Container', 'Piece of Heart']:
@@ -964,8 +992,7 @@ class CollectionState(object):
reduced[('Heart Container', player)] = 1
return frozenset(reduced.items())
- @staticmethod
- def check_if_progressive(item_name):
+ def check_if_progressive(self, item_name, player):
return (item_name in
['Bow', 'Progressive Bow', 'Progressive Bow (Alt)', 'Book of Mudora', 'Hammer', 'Hookshot',
'Magic Mirror', 'Ocarina', 'Pegasus Boots', 'Power Glove', 'Cape', 'Mushroom', 'Shovel',
@@ -974,10 +1001,11 @@ class CollectionState(object):
'Golden Sword', 'Progressive Sword', 'Progressive Glove', 'Silver Arrows', 'Green Pendant',
'Blue Pendant', 'Red Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5',
'Crystal 6', 'Crystal 7', 'Blue Boomerang', 'Red Boomerang', 'Blue Shield', 'Red Shield',
- 'Mirror Shield', 'Progressive Shield', 'Bug Catching Net', 'Cane of Byrna',
+ 'Mirror Shield', 'Progressive Shield', 'Bug Catching Net', 'Cane of Byrna', 'Ocarina (Activated)'
'Boss Heart Container', 'Sanctuary Heart Container', 'Piece of Heart', 'Magic Upgrade (1/2)',
'Magic Upgrade (1/4)']
- or item_name.startswith(('Bottle', 'Small Key', 'Big Key')))
+ or item_name.startswith(('Bottle', 'Small Key', 'Big Key'))
+ or (self.world.restrict_boss_items[player] != 'none' and item_name.startswith(('Map', 'Compass'))))
def can_reach(self, spot, resolution_hint=None, player=None):
try:
@@ -1087,6 +1115,9 @@ class CollectionState(object):
return True
return False
+ def can_collect_bonkdrops(self, player):
+ return self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))
+
def can_farm_rupees(self, player):
tree_pulls = ['Lost Woods East Area',
'Snitch Lady (East)',
@@ -1100,19 +1131,24 @@ class CollectionState(object):
rupee_farms = ['Archery Game', '50 Rupee Cave', '20 Rupee Cave']
+ bush_crabs = ['Lost Woods East Area', 'Mountain Entry Area']
+ pre_aga_bush_crabs = ['Lumberjack Area', 'South Pass Area']
+ rock_crabs = ['Desert Pass Area']
+
def can_reach_non_bunny(regionname):
region = self.world.get_region(regionname, player)
return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has('Pearl', player))
- for region in rupee_farms:
+ for region in rupee_farms if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else ['Archery Game']:
if can_reach_non_bunny(region):
return True
- if any(i in [0xda, 0xdb] for i in self.world.prizes[player]['pull']):
+ # tree pulls
+ if self.can_kill_most_things(player) and any(i in [0xda, 0xdb] for i in self.world.prizes[player]['pull']):
for region in tree_pulls:
if can_reach_non_bunny(region):
return True
- if not self.has('Beat Agahnim 1', player):
+ if not self.has_beaten_aga(player):
for region in pre_aga_tree_pulls:
if can_reach_non_bunny(region):
return True
@@ -1120,6 +1156,22 @@ class CollectionState(object):
for region in post_aga_tree_pulls:
if can_reach_non_bunny(region):
return True
+
+ # bush crabs (final item isn't considered)
+ if self.world.enemy_shuffle[player] == 'none':
+ if self.world.prizes[player]['crab'][0] in [0xda, 0xdb]:
+ for region in bush_crabs:
+ if can_reach_non_bunny(region):
+ return True
+ if not self.has_beaten_aga(player):
+ for region in pre_aga_bush_crabs:
+ if can_reach_non_bunny(region):
+ return True
+ if self.can_lift_rocks(player) and self.world.prizes[player]['crab'][0] in [0xda, 0xdb]:
+ for region in rock_crabs:
+ if can_reach_non_bunny(region):
+ return True
+
return False
def can_farm_bombs(self, player):
@@ -1169,7 +1221,7 @@ class CollectionState(object):
return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has('Pearl', player))
# bomb pickups
- for region in bush_bombs + bomb_caves:
+ for region in bush_bombs + (bomb_caves if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else []):
if can_reach_non_bunny(region):
return True
@@ -1178,17 +1230,17 @@ class CollectionState(object):
if can_reach_non_bunny(region):
return True
- if self.has_Boots(player):
+ if not self.world.shuffle_bonk_drops[player] and self.can_collect_bonkdrops(player):
for region in bonk_bombs:
if can_reach_non_bunny(region):
return True
# tree pulls
- if any(i in [0xdc, 0xdd, 0xde] for i in self.world.prizes[player]['pull']):
+ if self.can_kill_most_things(player) and any(i in [0xdc, 0xdd, 0xde] for i in self.world.prizes[player]['pull']):
for region in tree_pulls:
if can_reach_non_bunny(region):
return True
- if not self.has('Beat Agahnim 1', player):
+ if not self.has_beaten_aga(player):
for region in pre_aga_tree_pulls:
if can_reach_non_bunny(region):
return True
@@ -1198,12 +1250,12 @@ class CollectionState(object):
return True
# bush crabs (final item isn't considered)
- if self.world.enemy_shuffle[player] != 'none':
+ if self.world.enemy_shuffle[player] == 'none':
if self.world.prizes[player]['crab'][0] in [0xdc, 0xdd, 0xde]:
for region in bush_crabs:
if can_reach_non_bunny(region):
return True
- if not self.has('Beat Agahnim 1', player):
+ if not self.has_beaten_aga(player):
for region in pre_aga_bush_crabs:
if can_reach_non_bunny(region):
return True
@@ -1291,7 +1343,7 @@ class CollectionState(object):
def can_use_bombs(self, player):
if self.world.swords[player] == 'bombs':
return self.has_special_weapon_level(player, 1)
- return (not self.world.bombbag[player] or self.has('Bomb Upgrade (+10)', player)) and ((hasattr(self.world, "override_bomb_check") and self.world.override_bomb_check) or self.can_farm_bombs(player))
+ return (not self.world.bombbag[player] or self.has('Bomb Upgrade (+10)', player) or self.has('Bomb Upgrade (+5)', player, 2)) and (self.world.override_bomb_check or self.can_farm_bombs(player))
def can_kill_with_bombs(self, player):
if self.world.swords[player] in ['cane', 'somaria', 'byrna']:
@@ -1325,16 +1377,19 @@ class CollectionState(object):
return self.has('Bow', player) and (self.can_buy_unlimited('Single Arrow', player) or self.has('Single Arrow', player))
return self.has('Bow', player)
- def can_get_good_bee(self, player):
- cave = self.world.get_region('Good Bee Cave', player)
- return (
- self.can_use_bombs(player) and
- self.has_bottle(player) and
- self.has('Bug Catching Net', player) and
- (self.has_Boots(player) or (self.can_use_medallions(player) and self.has('Quake', player))) and
- cave.can_reach(self) and
- self.is_not_bunny(cave, player)
- )
+ # def can_get_good_bee(self, player):
+ # cave = self.world.get_region('Good Bee Cave', player)
+ # return (
+ # self.can_use_bombs(player) and
+ # self.has_bottle(player) and
+ # self.has('Bug Catching Net', player) and
+ # (self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))) and
+ # cave.can_reach(self) and
+ # self.is_not_bunny(cave, player)
+ # )
+
+ def has_beaten_aga(self, player):
+ return self.has('Beat Agahnim 1', player) and (self.world.mode[player] != 'standard' or self.has('Zelda Delivered', player))
def has_sword(self, player, level=1):
if level == 4:
@@ -1447,10 +1502,12 @@ class CollectionState(object):
return self.has('Fire Rod', player) or self.has('Lamp', player)
def can_flute(self, player):
+ if self.world.mode[player] == 'standard' and not self.has('Zelda Delivered', player):
+ return False
if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], self.world.precollected_items)):
return True
lw = self.world.get_region('Kakariko Area', player)
- return self.has('Ocarina Activated', player) or (self.has('Ocarina', player) and lw.can_reach(self) and self.is_not_bunny(lw, player))
+ return self.has('Ocarina (Activated)', player) or (self.has('Ocarina', player) and lw.can_reach(self) and self.is_not_bunny(lw, player))
def can_melt_things(self, player):
return self.has('Fire Rod', player) or (self.has('Bombos', player) and self.can_use_medallions(player))
@@ -1757,7 +1814,7 @@ class Region(object):
def can_reach(self, state):
from Utils import stack_size3a
from DungeonGenerator import GenerationException
- if stack_size3a() > 500:
+ if stack_size3a() > self.world.players * 500:
raise GenerationException(f'Infinite loop detected for "{self.name}" located at \'Region.can_reach\'')
if state.stale[self.player]:
@@ -2044,6 +2101,10 @@ class Dungeon(object):
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
+class FillError(RuntimeError):
+ pass
+
+
@unique
class DoorType(Enum):
Normal = 1
@@ -2418,7 +2479,6 @@ class Door(object):
class WorldType(IntEnum):
Light = 0
Dark = 1
- Special = 2
@unique
@@ -2434,6 +2494,8 @@ class OWEdge(object):
self.type = DoorType.Open
self.direction = direction
self.terrain = terrain
+ self.specialEntrance = False
+ self.specialExit = False
self.deadEnd = False
# rom properties
@@ -2447,6 +2509,7 @@ class OWEdge(object):
self.zeroHzCam = False
self.zeroVtCam = False
self.edge_id = edge_id
+ self.specialID = 0x0
self.midpoint = 0x0
self.linkOpp = 0x0
@@ -2458,12 +2521,10 @@ class OWEdge(object):
self.unknownX = 0x0
self.unknownY = 0x0
- if self.owIndex < 0x40:
+ if self.owIndex < 0x40 or self.owIndex >= 0x80:
self.worldType = WorldType.Light
- elif self.owIndex < 0x80:
- self.worldType = WorldType.Dark
else:
- self.worldType = WorldType.Special
+ self.worldType = WorldType.Dark
# logical properties
# self.connected = False # combine with Dest?
@@ -2481,7 +2542,7 @@ class OWEdge(object):
return base_address[self.direction] + (self.edge_id * 16)
def getTarget(self):
- return self.dest.edge_id
+ return self.dest.specialID if self.dest.specialExit else self.dest.edge_id
def dead_end(self):
self.deadEnd = True
@@ -2491,6 +2552,16 @@ class OWEdge(object):
self.vramLoc = vram_loc
return self
+ def special_entrance(self, special_id):
+ self.specialEntrance = True
+ self.specialID = special_id
+ return self
+
+ def special_exit(self, special_id):
+ self.specialExit = True
+ self.specialID = special_id
+ return self
+
def __eq__(self, other):
return isinstance(other, self.__class__) and self.name == other.name
@@ -2525,6 +2596,8 @@ class Sector(object):
self.destination_entrance = False
self.equations = None
self.item_logic = set()
+ self.chest_location_set = set()
+
def region_set(self):
if self.r_name_set is None:
@@ -2633,6 +2706,12 @@ class Portal(object):
self.dependent = None
self.deadEnd = False
self.light_world = False
+ self.chosen = False
+
+ def find_portal_entrance(self):
+ p_region = self.door.entrance.connected_region
+ return next((x for x in p_region.entrances
+ if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]), None)
def change_boss_exit(self, exit_idx):
self.default = False
@@ -2767,15 +2846,25 @@ class Location(object):
self.recursion_count = 0
self.staleness_count = 0
self.locked = False
+ self.real = not crystal
self.always_allow = lambda item, state: False
self.access_rule = lambda state: True
self.item_rule = lambda item: True
self.player = player
self.skip = False
+ self.type = LocationType.Normal if not crystal else LocationType.Prize
+ self.pot = None
def can_fill(self, state, item, check_access=True):
+ if not self.valid_multiworld(state, item):
+ return False
return self.always_allow(state, item) or (self.parent_region.can_fill(item) and self.item_rule(item) and (not check_access or self.can_reach(state)))
+ def valid_multiworld(self, state, item):
+ if self.type == LocationType.Pot and self.player != item.player:
+ return state.world.pot_contents[self.player].multiworld_count < 256
+ return True
+
def can_reach(self, state):
return self.parent_region.can_reach(state) and self.access_rule(state)
@@ -2811,6 +2900,16 @@ class Location(object):
return hash((self.name, self.player))
+class LocationType(FastEnum):
+ Normal = 0
+ Prize = 1
+ Logical = 2
+ Shop = 3
+ Pot = 4
+ Drop = 5
+ Bonk = 6
+
+
class Item(object):
def __init__(self, name='', advancement=False, priority=False, type=None, code=None, price=999, pedestal_hint=None,
@@ -2862,6 +2961,12 @@ class Item(object):
item_dungeon = 'Hyrule Castle'
return item_dungeon
+ def is_inside_dungeon_item(self, world):
+ return ((self.smallkey and not world.keyshuffle[self.player])
+ or (self.bigkey and not world.bigkeyshuffle[self.player])
+ or (self.compass and not world.compassshuffle[self.player])
+ or (self.map and not world.mapshuffle[self.player]))
+
def __str__(self):
return str(self.__unicode__())
@@ -3018,11 +3123,14 @@ class Spoiler(object):
'ow_mixed': self.world.owMixed,
'ow_whirlpool': self.world.owWhirlpoolShuffle,
'ow_fluteshuffle': self.world.owFluteShuffle,
+ 'bonk_drops': self.world.shuffle_bonk_drops,
'shuffle': self.world.shuffle,
'shuffleganon': self.world.shuffle_ganon,
'shufflelinks': self.world.shufflelinks,
+ 'overworld_map': self.world.overworld_map,
'door_shuffle': self.world.doorShuffle,
'intensity': self.world.intensity,
+ 'dungeon_counters': self.world.dungeon_counters,
'item_pool': self.world.difficulty,
'item_functionality': self.world.difficulty_adjustments,
'gt_crystals': self.world.crystals_needed_for_gt,
@@ -3030,6 +3138,7 @@ class Spoiler(object):
'ganon_vulnerability_item': self.world.ganon_item,
'open_pyramid': self.world.open_pyramid,
'accessibility': self.world.accessibility,
+ 'restricted_boss_items': self.world.restrict_boss_items,
'hints': self.world.hints,
'mapshuffle': self.world.mapshuffle,
'compassshuffle': self.world.compassshuffle,
@@ -3039,16 +3148,19 @@ class Spoiler(object):
'enemy_shuffle': self.world.enemy_shuffle,
'enemy_health': self.world.enemy_health,
'enemy_damage': self.world.enemy_damage,
- 'potshuffle': self.world.potshuffle,
'players': self.world.players,
'teams': self.world.teams,
'experimental': self.world.experimental,
- 'keydropshuffle': self.world.keydropshuffle,
+ 'dropshuffle': self.world.dropshuffle,
+ 'pottery': self.world.pottery,
+ 'potshuffle': self.world.potshuffle,
'shopsanity': self.world.shopsanity,
+ 'pseudoboots': self.world.pseudoboots,
'triforcegoal': self.world.treasure_hunt_count,
'triforcepool': self.world.treasure_hunt_total,
'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)}
}
+
for p in range(1, self.world.players + 1):
from ItemList import set_default_triforce
if self.world.custom and p in self.world.customitemarray:
@@ -3076,7 +3188,7 @@ class Spoiler(object):
for player in range(1, self.world.players + 1):
self.bottles[f'Waterfall Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][0]
self.bottles[f'Pyramid Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][1]
-
+
self.locations = OrderedDict()
listed_locations = set()
@@ -3093,11 +3205,11 @@ class Spoiler(object):
listed_locations.update(cave_locations)
for dungeon in self.world.dungeons:
- dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and not loc.forced_item]
+ dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and not loc.forced_item and not loc.skip]
self.locations[str(dungeon)] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations])
listed_locations.update(dungeon_locations)
- other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations]
+ other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and not loc.skip]
if other_locations:
self.locations['Other Locations'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in other_locations])
listed_locations.update(other_locations)
@@ -3172,11 +3284,26 @@ class Spoiler(object):
return json.dumps(out)
- def meta_to_file(self, filename):
+ def mystery_meta_to_file(self, filename):
self.parse_meta()
with open(filename, 'w') as outfile:
line_width = 35
- outfile.write('ALttP Entrance Randomizer - Seed: %s\n\n' % (self.world.seed))
+ outfile.write('ALttP Overworld Randomizer - Seed: %s\n\n' % (self.world.seed))
+ for k,v in self.metadata["versions"].items():
+ outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v)
+ for player in range(1, self.world.players + 1):
+ if self.world.players > 1:
+ outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_names(player)))
+ outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player])
+
+ def meta_to_file(self, filename):
+ def yn(flag):
+ return 'Yes' if flag else 'No'
+
+ self.parse_meta()
+ with open(filename, 'w') as outfile:
+ line_width = 35
+ outfile.write('ALttP Overworld Randomizer - Seed: %s\n\n' % (self.world.seed))
for k,v in self.metadata["versions"].items():
outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v)
outfile.write('Filling Algorithm:'.ljust(line_width) + '%s\n' % self.world.algorithm)
@@ -3188,7 +3315,7 @@ class Spoiler(object):
outfile.write('Settings Code:'.ljust(line_width) + '%s\n' % self.metadata["code"][player])
outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player])
outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player])
- outfile.write('Retro:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['retro'][player] else 'No'))
+ outfile.write('Retro:'.ljust(line_width) + '%s\n' % yn(self.metadata['retro'][player]))
outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player])
outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player])
if self.metadata['goal'][player] in ['triforcehunt', 'trinity']:
@@ -3198,56 +3325,80 @@ class Spoiler(object):
outfile.write('Crystals Required for Ganon:'.ljust(line_width) + '%s\n' % str(self.world.crystals_ganon_orig[player]))
outfile.write('Ganon Vulnerability Item:'.ljust(line_width) + '%s\n' % str(self.metadata['ganon_vulnerability_item'][player]))
outfile.write('Accessibility:'.ljust(line_width) + '%s\n' % self.metadata['accessibility'][player])
+ outfile.write('Restricted Boss Items:'.ljust(line_width) + '%s\n' % self.metadata['restricted_boss_items'][player])
outfile.write('Difficulty:'.ljust(line_width) + '%s\n' % self.metadata['item_pool'][player])
outfile.write('Item Functionality:'.ljust(line_width) + '%s\n' % self.metadata['item_functionality'][player])
- outfile.write('Shopsanity:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shopsanity'][player] else 'No'))
- outfile.write('Bombbag:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['bombbag'][player] else 'No'))
+ outfile.write('Shopsanity:'.ljust(line_width) + '%s\n' % yn(self.metadata['shopsanity'][player]))
+ outfile.write('Bombbag:'.ljust(line_width) + '%s\n' % yn(self.metadata['bombbag'][player]))
+ outfile.write('Pseudoboots:'.ljust(line_width) + '%s\n' % yn(self.metadata['pseudoboots'][player]))
outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player])
- if self.metadata['ow_shuffle'][player] != 'vanilla':
- outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_keepsimilar'][player] else 'No'))
outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player])
- outfile.write('Swapped OW (Mixed):'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_mixed'][player] else 'No'))
- outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_whirlpool'][player] else 'No'))
+ if self.metadata['ow_shuffle'][player] != 'vanilla' or self.metadata['ow_crossed'][player] != 'none':
+ outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_keepsimilar'][player]))
+ outfile.write('Swapped OW (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player]))
+ outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_whirlpool'][player]))
outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player])
+ outfile.write('Bonk Drops:'.ljust(line_width) + '%s\n' % yn(self.metadata['bonk_drops'][player]))
outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player])
if self.metadata['shuffle'][player] != 'vanilla':
- outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shuffleganon'][player] else 'No'))
- outfile.write('Shuffle Links:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shufflelinks'][player] else 'No'))
- if self.metadata['goal'][player] != 'trinity':
- outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['open_pyramid'][player] else 'No'))
+ outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffleganon'][player]))
+ outfile.write('Shuffle Links:'.ljust(line_width) + '%s\n' % yn(self.metadata['shufflelinks'][player]))
+ if self.metadata['shuffle'][player] != 'vanilla' or self.metadata['ow_mixed'][player]:
+ outfile.write('Overworld Map:'.ljust(line_width) + '%s\n' % self.metadata['overworld_map'][player])
+ outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % self.metadata['open_pyramid'][player])
outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player])
if self.metadata['door_shuffle'][player] != 'vanilla':
outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player])
- outfile.write('Experimental:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['experimental'][player] else 'No'))
- outfile.write('Pot Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['potshuffle'][player] else 'No'))
- outfile.write('Key Drop Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['keydropshuffle'][player] else 'No'))
- outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['mapshuffle'][player] else 'No'))
- outfile.write('Compass Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['compassshuffle'][player] else 'No'))
- outfile.write('Small Key Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['keyshuffle'][player] else 'No'))
- outfile.write('Big Key Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['bigkeyshuffle'][player] else 'No'))
+ outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player]))
+ outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player])
+ outfile.write('Enemy Drop Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['dropshuffle'][player]))
+ outfile.write('Pottery Mode:'.ljust(line_width) + '%s\n' % self.metadata['pottery'][player])
+ outfile.write('Pot Shuffle (Legacy):'.ljust(line_width) + '%s\n' % yn(self.metadata['potshuffle'][player]))
+ outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['mapshuffle'][player]))
+ outfile.write('Compass Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['compassshuffle'][player]))
+ outfile.write('Small Key Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['keyshuffle'][player]))
+ outfile.write('Big Key Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['bigkeyshuffle'][player]))
outfile.write('Boss Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['boss_shuffle'][player])
outfile.write('Enemy Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['enemy_shuffle'][player])
outfile.write('Enemy Health:'.ljust(line_width) + '%s\n' % self.metadata['enemy_health'][player])
outfile.write('Enemy Damage:'.ljust(line_width) + '%s\n' % self.metadata['enemy_damage'][player])
- outfile.write('Hints:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['hints'][player] else 'No'))
+ outfile.write('Hints:'.ljust(line_width) + '%s\n' % yn(self.metadata['hints'][player]))
if self.startinventory:
outfile.write('Starting Inventory:'.ljust(line_width))
outfile.write('\n'.ljust(line_width+1).join(self.startinventory) + '\n')
+ def hashes_to_file(self, filename):
+ with open(filename, 'r') as infile:
+ contents = infile.readlines()
+
+ def insert(lines, i, value):
+ lines.insert(i, value)
+ i += 1
+ return i
+
+ idx = 2
+ if self.world.players > 1:
+ idx = insert(contents, idx, 'Hashes:')
+ for player in range(1, self.world.players + 1):
+ if self.world.players > 1:
+ idx = insert(contents, idx, f'\nPlayer {player}: {self.world.get_player_names(player)}\n')
+ if len(self.hashes) > 0:
+ for team in range(self.world.teams):
+ player_name = self.world.player_names[player][team]
+ label = f"Hash - {player_name} (Team {team+1}): " if self.world.teams > 1 else 'Hash: '
+ idx = insert(contents, idx, f'{label}{self.hashes[player, team]}\n')
+ if self.world.players > 1:
+ insert(contents, idx, '\n') # return value ignored here, if you want to add more lines
+
+ with open(filename, "w") as f:
+ contents = "".join(contents)
+ f.write(contents)
+
def to_file(self, filename):
self.parse_data()
with open(filename, 'a') as outfile:
line_width = 35
- if self.world.players > 1:
- outfile.write('\nHashes:')
- for player in range(1, self.world.players + 1):
- if self.world.players > 1:
- outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_names(player)))
- if len(self.hashes) > 0:
- for team in range(self.world.teams):
- outfile.write('%s%s\n' % (f"Hash - {self.world.player_names[player][team]} (Team {team+1}): " if self.world.teams > 1 else 'Hash: ', self.hashes[player, team]))
-
outfile.write('\n\nRequirements:\n\n')
for dungeon, medallion in self.medallions.items():
outfile.write(f'{dungeon}:'.ljust(line_width) + '%s Medallion\n' % medallion)
@@ -3262,7 +3413,7 @@ class Spoiler(object):
for fairy, bottle in self.bottles.items():
outfile.write(f'{fairy}: {bottle}\n')
- if self.overworlds:
+ if self.overworlds or self.maps:
outfile.write('\n\nOverworld:\n\n')
# flute shuffle
@@ -3286,7 +3437,19 @@ class Spoiler(object):
if self.world.players > 1:
outfile.write(str('(Player ' + str(player) + ')\n')) # player name
outfile.write(self.maps[('swaps', player)]['text'] + '\n\n')
-
+
+ # crossed groups
+ for player in range(1, self.world.players + 1):
+ if ('groups', player) in self.maps:
+ outfile.write('OW Crossed Groups:\n')
+ break
+ for player in range(1, self.world.players + 1):
+ if ('groups', player) in self.maps:
+ if self.world.players > 1:
+ outfile.write(str('(Player ' + str(player) + ')\n')) # player name
+ outfile.write(self.maps[('groups', player)]['text'] + '\n\n')
+
+ if self.overworlds:
# overworld transitions
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","overworlds",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","overworlds",entry['exit'])) for entry in self.overworlds.values()]))
@@ -3332,7 +3495,7 @@ class Spoiler(object):
outfile.write(f'\n\nBosses ({self.world.get_player_names(player)}):\n\n')
outfile.write('\n'.join([f'{x}: {y}' for x, y in bossmap.items() if y not in ['Agahnim', 'Agahnim 2', 'Ganon']]))
- def playthru_to_file(self, filename):
+ def playthrough_to_file(self, filename):
with open(filename, 'a') as outfile:
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
# items: Item names
@@ -3430,15 +3593,40 @@ class PotFlags(FastEnum):
Normal = 0x0
NoSwitch = 0x1 # A switch should never go here
SwitchLogicChange = 0x2 # A switch can go here, but requires a logic change
+ Block = 0x4 # This is actually a block
+ LowerRegion = 0x8 # This is a pot in the lower region
class Pot(object):
- def __init__(self, x, y, item, room, flags = PotFlags.Normal):
+ def __init__(self, x, y, item, room, flags=PotFlags.Normal, obj=None):
self.x = x
self.y = y
self.item = item
self.room = room
self.flags = flags
+ self.indicator = None # 0x80 for standing item, 0xC0 multiworld item
+ self.standing_item_code = None # standing item code if nay
+ self.obj_ref = obj
+ self.location = None # location back ref
+
+ def copy(self):
+ obj_ref = RoomObject(self.obj_ref.address, self.obj_ref.data) if self.obj_ref else None
+ return Pot(self.x, self.y, self.item, self.room, self.flags, obj_ref)
+
+ def pot_data(self):
+ high_byte = self.y
+ if self.flags & PotFlags.LowerRegion:
+ high_byte |= 0x20
+ if self.indicator:
+ high_byte |= self.indicator
+ item = self.item if not self.indicator else self.standing_item_code
+ return [self.x, high_byte, item]
+
+ def __eq__(self, other):
+ return self.x == other.x and self.y == other.y and self.room == other.room
+
+ def __hash__(self):
+ return hash((self.x, self.y, self.room))
# byte 0: DDOO OEEE (DR, OR, ER)
@@ -3456,24 +3644,44 @@ goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "cryst
diff_mode = {"normal": 0, "hard": 1, "expert": 2}
func_mode = {"normal": 0, "hard": 1, "expert": 2}
-# byte 3: SKMM PIII (shop, keydrop, mixed, palettes, intensity)
+# byte 3: S?MM PIII (shop, unused, mixed, palettes, intensity)
+# keydrop now has it's own byte
mixed_travel_mode = {"prevent": 0, "allow": 1, "force": 2}
# intensity is 3 bits (reserves 4-7 levels)
-# byte 4: CCCC CTTX (crystals gt, ctr2, experimental)
+# new byte 4: ?DDD PPPP (unused, drop, pottery)
+# dropshuffle reserves 2 bits, pottery needs 2 but reserves 2 for future modes)
+pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7,
+ 'clustered': 8, 'nonempty': 9}
+
+# byte 5: CCCC CTTX (crystals gt, ctr2, experimental)
counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3}
-# byte 5: CCCC CPAA (crystals ganon, pyramid, access
+# byte 6: CCCC CPAA (crystals ganon, pyramid, access
access_mode = {"items": 0, "locations": 1, "none": 2}
-# byte 6: BSMC BBEE (big, small, maps, compass, bosses, enemies)
-boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3, "chaos": 3}
-enemy_mode = {"none": 0, "shuffled": 1, "random": 2, "chaos": 2, "legacy": 3}
+# byte 7: BSMC ??EE (big, small, maps, compass, bosses, enemies)
+enemy_mode = {"none": 0, "shuffled": 1, "chaos": 2, "random": 2, "legacy": 3}
-# byte 7: HHHD DPBS (enemy_health, enemy_dmg, potshuffle, bomb logic, shuffle links)
+# byte 8: HHHD DPBS (enemy_health, enemy_dmg, potshuffle, bomb logic, shuffle links)
+# potshuffle decprecated, now unused
e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4}
e_dmg = {"default": 0, "shuffled": 1, "random": 2}
+# byte 9: RRAA ABBB (restrict boss mode, algorithm, boss shuffle)
+rb_mode = {"none": 0, "mapcompass": 1, "dungeon": 2}
+# algorithm:
+algo_mode = {"balanced": 0, "equitable": 1, "vanilla_fill": 2, "dungeon_only": 3, "district": 4, 'major_only': 5}
+boss_mode = {"none": 0, "simple": 1, "full": 2, "chaos": 3, 'random': 3, 'unique': 4}
+
+# additions
+# psuedoboots does not effect code
+# sfx_shuffle and other adjust items does not effect settings code
+
+# Bump this when making changes that are not backwards compatible (nearly all of them)
+settings_version = 0
+
+
class Settings(object):
@staticmethod
@@ -3486,28 +3694,39 @@ class Settings(object):
(goal_mode[w.goal[p]] << 5) | (diff_mode[w.difficulty[p]] << 3)
| (func_mode[w.difficulty_adjustments[p]] << 1) | (1 if w.hints[p] else 0),
- (0x80 if w.shopsanity[p] else 0) | (0x40 if w.keydropshuffle[p] else 0)
- | (mixed_travel_mode[w.mixed_travel[p]] << 4) | (0x8 if w.standardize_palettes[p] == "original" else 0)
+ (0x80 if w.shopsanity[p] else 0) | (mixed_travel_mode[w.mixed_travel[p]] << 4)
+ | (0x8 if w.standardize_palettes[p] == "original" else 0)
| (0 if w.intensity[p] == "random" else w.intensity[p]),
+ (0x10 if w.dropshuffle[p] else 0) | (pottery_mode[w.pottery[p]]),
+
((8 if w.crystals_gt_orig[p] == "random" else int(w.crystals_gt_orig[p])) << 3)
| (counter_mode[w.dungeon_counters[p]] << 1) | (1 if w.experimental[p] else 0),
((8 if w.crystals_ganon_orig[p] == "random" else int(w.crystals_ganon_orig[p])) << 3)
- | (0x4 if w.open_pyramid[p] else 0) | access_mode[w.accessibility[p]],
+ | (0x4 if w.is_pyramid_open(p) else 0) | access_mode[w.accessibility[p]],
(0x80 if w.bigkeyshuffle[p] else 0) | (0x40 if w.keyshuffle[p] else 0)
| (0x20 if w.mapshuffle[p] else 0) | (0x10 if w.compassshuffle[p] else 0)
- | (boss_mode[w.boss_shuffle[p]] << 2) | (enemy_mode[w.enemy_shuffle[p]]),
+ | (enemy_mode[w.enemy_shuffle[p]]),
(e_health[w.enemy_health[p]] << 5) | (e_dmg[w.enemy_damage[p]] << 3) | (0x4 if w.potshuffle[p] else 0)
- | (0x2 if w.bombbag[p] else 0) | (1 if w.shufflelinks[p] else 0)])
+ | (0x2 if w.bombbag[p] else 0) | (1 if w.shufflelinks[p] else 0),
+
+ (rb_mode[w.restrict_boss_items[p]] << 6) | (algo_mode[w.algorithm] << 3) | (boss_mode[w.boss_shuffle[p]]),
+
+ settings_version])
return base64.b64encode(code, "+-".encode()).decode()
@staticmethod
def adjust_args_from_code(code, player, args):
settings, p = base64.b64decode(code.encode(), "+-".encode()), player
+ if len(settings) < 11:
+ raise Exception('Provided code is incompatible with this version')
+ if settings[10] != settings_version:
+ raise Exception('Provided code is incompatible with this version')
+
def r(d):
return {y: x for x, y in d.items()}
@@ -3520,34 +3739,45 @@ class Settings(object):
args.difficulty[p] = r(diff_mode)[(settings[2] & 0x18) >> 3]
args.item_functionality[p] = r(func_mode)[(settings[2] & 0x6) >> 1]
args.goal[p] = r(goal_mode)[(settings[2] & 0xE0) >> 5]
- args.accessibility[p] = r(access_mode)[settings[5] & 0x3]
+ args.accessibility[p] = r(access_mode)[settings[6] & 0x3]
args.retro[p] = True if settings[1] & 0x01 else False
args.hints[p] = True if settings[2] & 0x01 else False
- args.retro[p] = True if settings[1] & 0x01 else False
args.shopsanity[p] = True if settings[3] & 0x80 else False
- args.keydropshuffle[p] = True if settings[3] & 0x40 else False
+ # args.keydropshuffle[p] = True if settings[3] & 0x40 else False
args.mixed_travel[p] = r(mixed_travel_mode)[(settings[3] & 0x30) >> 4]
args.standardize_palettes[p] = "original" if settings[3] & 0x8 else "standardize"
intensity = settings[3] & 0x7
args.intensity[p] = "random" if intensity == 0 else intensity
- args.dungeon_counters[p] = r(counter_mode)[(settings[4] & 0x6) >> 1]
- cgt = (settings[4] & 0xf8) >> 3
+
+ # args.shuffleswitches[p] = True if settings[4] & 0x80 else False
+ args.dropshuffle[p] = True if settings[4] & 0x10 else False
+ args.pottery[p] = r(pottery_mode)[settings[4] & 0x0F]
+
+ args.dungeon_counters[p] = r(counter_mode)[(settings[5] & 0x6) >> 1]
+ cgt = (settings[5] & 0xf8) >> 3
args.crystals_gt[p] = "random" if cgt == 8 else cgt
- args.experimental[p] = True if settings[4] & 0x1 else False
- cgan = (settings[5] & 0xf8) >> 3
+ args.experimental[p] = True if settings[5] & 0x1 else False
+
+ cgan = (settings[6] & 0xf8) >> 3
args.crystals_ganon[p] = "random" if cgan == 8 else cgan
- args.openpyramid[p] = True if settings[5] & 0x4 else False
- args.bigkeyshuffle[p] = True if settings[6] & 0x80 else False
- args.keyshuffle[p] = True if settings[6] & 0x40 else False
- args.mapshuffle[p] = True if settings[6] & 0x20 else False
- args.compassshuffle[p] = True if settings[6] & 0x10 else False
- args.shufflebosses[p] = r(boss_mode)[(settings[6] & 0xc) >> 2]
- args.shuffleenemies[p] = r(enemy_mode)[settings[6] & 0x3]
- args.enemy_health[p] = r(e_health)[(settings[7] & 0xE0) >> 5]
- args.enemy_damage[p] = r(e_dmg)[(settings[7] & 0x18) >> 3]
- args.shufflepots[p] = True if settings[7] & 0x4 else False
- args.bombbag[p] = True if settings[7] & 0x2 else False
- args.shufflelinks[p] = True if settings[7] & 0x1 else False
+ args.openpyramid[p] = True if settings[6] & 0x4 else False
+
+ args.bigkeyshuffle[p] = True if settings[7] & 0x80 else False
+ args.keyshuffle[p] = True if settings[7] & 0x40 else False
+ args.mapshuffle[p] = True if settings[7] & 0x20 else False
+ args.compassshuffle[p] = True if settings[7] & 0x10 else False
+ # args.shufflebosses[p] = r(boss_mode)[(settings[7] & 0xc) >> 2]
+ args.shuffleenemies[p] = r(enemy_mode)[settings[7] & 0x3]
+
+ args.enemy_health[p] = r(e_health)[(settings[8] & 0xE0) >> 5]
+ args.enemy_damage[p] = r(e_dmg)[(settings[8] & 0x18) >> 3]
+ args.shufflepots[p] = True if settings[8] & 0x4 else False
+ args.bombbag[p] = True if settings[8] & 0x2 else False
+ args.shufflelinks[p] = True if settings[8] & 0x1 else False
+ if len(settings) > 9:
+ args.restrict_boss_items[p] = r(rb_mode)[(settings[9] & 0xC0) >> 6]
+ args.algorithm = r(algo_mode)[(settings[9] & 0x38) >> 3]
+ args.shufflebosses[p] = r(boss_mode)[(settings[9] & 0x07)]
class KeyRuleType(FastEnum):
diff --git a/Bosses.py b/Bosses.py
index 1f691c26..77a61c58 100644
--- a/Bosses.py
+++ b/Bosses.py
@@ -1,8 +1,8 @@
import logging
import RaceRandom as random
-from BaseClasses import Boss
-from Fill import FillError
+from BaseClasses import Boss, FillError
+
def BossFactory(boss, player):
if boss is None:
@@ -71,7 +71,6 @@ def MothulaDefeatRule(state, player):
# to non-vanilla locations, so are harder to test, so sticking with what VT has for now:
(state.has('Cane of Somaria', player) and state.can_extend_magic(player, 16)) or
(state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) or
- state.can_get_good_bee(player) or
state.has_special_weapon_level(player, 1)))
def BlindDefeatRule(state, player):
@@ -183,18 +182,19 @@ def place_bosses(world, player):
all_bosses = sorted(boss_table.keys()) #s orted to be deterministic on older pythons
placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']]
- if world.boss_shuffle[player] in ["simple", "full"]:
- # temporary hack for swordless kholdstare:
+ # temporary hack for swordless kholdstare:
+ if world.boss_shuffle[player] in ["simple", "full", "unique"]:
if world.swords[player] == 'swordless':
world.get_dungeon('Ice Palace', player).boss = BossFactory('Kholdstare', player)
logging.getLogger('').debug('Placing boss Kholdstare at Ice Palace')
boss_locations.remove(['Ice Palace', None])
placeable_bosses.remove('Kholdstare')
+ if world.boss_shuffle[player] in ["simple", "full"]:
if world.boss_shuffle[player] == "simple": # vanilla bosses shuffled
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
else: # all bosses present, the three duplicates chosen at random
- bosses = all_bosses + random.sample(placeable_bosses, 3)
+ bosses = placeable_bosses + random.sample(placeable_bosses, 3)
logging.getLogger('').debug('Bosses chosen %s', bosses)
@@ -206,12 +206,7 @@ def place_bosses(world, player):
raise FillError('Could not place boss for location %s' % loc_text)
bosses.remove(boss)
- # GT Bosses can move dungeon - find the real dungeon to place them in
- if level:
- loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0]
- loc_text = loc + ' (' + level + ')'
- logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text)
- world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player)
+ place_boss(boss, level, loc, loc_text, world, player)
elif world.boss_shuffle[player] == "random": #all bosses chosen at random
for [loc, level] in boss_locations:
loc_text = loc + (' ('+level+')' if level else '')
@@ -220,9 +215,31 @@ def place_bosses(world, player):
except IndexError:
raise FillError('Could not place boss for location %s' % loc_text)
- # GT Bosses can move dungeon - find the real dungeon to place them in
- if level:
- loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0]
- loc_text = loc + ' (' + level + ')'
- logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text)
- world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player)
+ place_boss(boss, level, loc, loc_text, world, player)
+ elif world.boss_shuffle[player] == 'unique':
+ bosses = list(placeable_bosses)
+ gt_bosses = []
+
+ for [loc, level] in boss_locations:
+ loc_text = loc + (' ('+level+')' if level else '')
+ try:
+ if level:
+ boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)
+ and b not in gt_bosses])
+ gt_bosses.append(boss)
+ else:
+ boss = random.choice([b for b in bosses if can_place_boss(world, player, b, loc, level)])
+ bosses.remove(boss)
+ except IndexError:
+ raise FillError('Could not place boss for location %s' % loc_text)
+
+ place_boss(boss, level, loc, loc_text, world, player)
+
+
+def place_boss(boss, level, loc, loc_text, world, player):
+ # GT Bosses can move dungeon - find the real dungeon to place them in
+ if level:
+ loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0]
+ loc_text = loc + ' (' + level + ')'
+ logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text)
+ world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a28cb537..d0a059de 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,77 @@
# Changelog
+## 0.2.10.1
+- Merged DR v1.0.1.3
+ - Fixed Zelda despawn in TT Prison
+ - Fixed issue with key door usage in rainstate
+- Added missing modes to example mystery yaml
+
+## 0.2.10.0
+- Merged DR v1.0.1.1-1.0.1.2
+ - Removed text color from hint tiles
+ - Removed Good Bee requirement from Mothula
+ - Some keylogic/generation fixes
+ - Fixed a Pottery logic issue in the playthru
+- Fixed a generation error in Mixed OWR, resulting in more possible Mixed scenarios (thanks Catobat)
+- Added more scenarios where OW Map Checks in Mixed OWR show dungeon prizes in their respective worlds
+- Fixed rupee logic to consider Pottery option and lack of early rupees
+- Changed Lean ER + Inverted Dark Chapel start is guaranteed to be in DW
+- Fixed graphical issue with Hammerpeg Cave
+- Fixed logic rule with HC Main Gate to not require mirror if screen is swapped
+- Removed Crossed OWR option: "None (Allowed)"
+
+### 0.2.9.1
+- Lite/Lean ER now includes Cave Pot locations with various Pottery options
+- Changed Unique Boss Shuffle so that GT Bosses are unique amongst themselves
+- Changed MSU-1 in Inverted to trigger DW2 track with Aga1 kill and LW2 with 7 crystals
+- Fixed disappearing mirror portal issue in Inverted (Hopefully for good)
+- Fixed issue with TR Peg Puzzle not spawning portal in some Mixed OWR scenarios
+- Removed ability to roll Myserty with phantom Crossed OWR options
+
+### 0.2.9.0
+- Added Bonk Drop Shuffle
+- Fixed disappearing mirror portal issue in Inverted+Crossed OWR
+- Fixed 4-digit collection rate in credits
+- Fixed Ganon vulnerability to reference Aga2 boss flag rather than pyramid hole
+- Fixed issue with pre-opened pyramid when not expected
+
+### 0.2.8.0
+- ~Merged DR v1.0.1.0 - Pottery options, BPS support, MSU Resume, Collection Rate Counter~
+- Various improvements to increase generation success rate and reduce generation time
+- Fixed issue with playthru recognizing Aga accessibility
+- Fixed issue with applying rules correctly to Murahdahla, fixing Murahdahla+Beatable issues
+- Fixed issue with Flute+Rainstate, flute use is no longer in logic until Zelda is delivered
+
+### 0.2.7.3
+- Restructured OWR algorithm to include some additional scenarios not previously allowed
+- Added new Inverted D-pad controls for Social Distortion (ie. Mirror Mode) support
+- Crossed OWR/Special OW Areas are now included in the spoiler log
+- Fixed default TF pieces with Trinity in Mystery
+- Added bush crabs to rupee farm logic (only in non-enemizer)
+- Updated tree pull logic to also require ability to kill most things
+
+### 0.2.7.2
+- Special OW Areas are now shuffled in Layout Shuffle (Zora/Hobo/Pedestal)
+- Fixed some broken water region graph modelling, fixed some reachability logic
+- Some minor code simplifications
+
+### 0.2.7.1
+- Map checks in Mixed OWR now will show the proper tile images when screens are swapped (ie. Pyramid shows in the LW if that screen is swapped)
+- Added mystery seed number to spoiler log, so it is easier to match a spoiler log to a rom filename
+- Added proper branch-specific versioning (ie. Dev branch has '-u' suffixing the version number while Release/Main branch does not)
+
+### 0.2.7.0
+- ~Merged DR v1.0.0.3 - MANY changes, major things listed below~
+ - New Item Fills (Districts/Vanilla/Major Location/Dungeon)
+ - New OW Map Prize Indicators (In ER, map checks can spoil dungeon locations with a user setting)
+ - Forbidden Boss Items (Exclude certain dungeon items from dropping on bosses)
+- Map checks in Mixed OWR now will show dungeon prizes for dungeons actually in the world you map check on
+- In Mixed OWR, Sanc screen must stay in LW if the starting location is guaranteed to exit at the Sanc entrance
+- Fixed various issues with Flute logic
+- Fixed issue that resulted in infinite loops in Flute Shuffle
+- Changed map in attract mode to always show a vanilla LW map
+- Various improvements to increase generation success rate
+
### 0.2.6.1
- Fixed issue with mirror bonking deleting portal in Crossed OW
- Fixed issue with mirror portal not spawning when entering the OW from the DW, in Crossed OW
@@ -40,7 +112,7 @@
- Fixed issue with incorrect Mirror bonking
- Fixed issue with old man follower death to Pyramid
- Fixed Hera boss music not playing when boss not defeated
-- ~~Merged DR v0.5.1.7 - TT boss trap door fix/Applied Glitched flag~~
+- ~Merged DR v0.5.1.7 - TT boss trap door fix/Applied Glitched flag~
### 0.2.4.0
- Added Guaranteed OWR Reachability
@@ -66,7 +138,7 @@
- Fake flipper damage fix improved to skip the long delay after the scroll
- Fixed missing Blue Potion in Lake Shop in Inverted
- Added legacy OW Crossed option 'None (Allowed)' to support old behavior when invalid option was used in Mystery
-- ~~Merged DR v0.5.1.6 - Money balancing fix/Boss logic fixes with Bombbag~~
+- ~Merged DR v0.5.1.6 - Money balancing fix/Boss logic fixes with Bombbag~
### 0.2.3.3
- Added OW Layout validation that reduces the cases where some screens are unreachable
@@ -108,7 +180,7 @@
- Fixed music track change to Sanc music when Standard mode is delivering Zelda
- Fixed SP flooding issue
- Fixed issue with Shuffle Ganon in CLI/GUI
-- ~~Merged DR v0.5.1.5 - Mystery subweights~~
+- ~Merged DR v0.5.1.5 - Mystery subweights~
### 0.2.1.2
- Fixed issue with whirlpools not changing world when in Crossed OW
@@ -129,7 +201,7 @@
- Smith deletion on S+Q only occurs if Blacksmith not reachable from starting locations
- Spoiler log improvements to prevent spoiling in the beginning 'meta' section
- Various minor fixes and improvements
-- ~~Merged DR v0.5.1.4 - ROM bug fixes/keylogic improvements~~
+- ~Merged DR v0.5.1.4 - ROM bug fixes/keylogic improvements~
### 0.1.9.4
- Hotfix for bad 0.1.9.3 version
@@ -142,11 +214,11 @@
### 0.1.9.2
- Fixed spoiler log and mystery for new Crossed/Mixed structure
- Minor preparations and tweaks to ER framework (added global Entrance/Exit pool)
-- ~~Merged DR v0.5.1.2 - Blind Prison shuffled outside TT/Keylogic Improvements~~
+- ~Merged DR v0.5.1.2 - Blind Prison shuffled outside TT/Keylogic Improvements~
### 0.1.9.1
- Fixed logic issue with leaving IP entrance not requiring flippers
-- ~~Merged DR v0.5.1.1 - Map Indicator Fix/Boss Shuffle Bias/Shop Hints~~
+- ~Merged DR v0.5.1.1 - Map Indicator Fix/Boss Shuffle Bias/Shop Hints~
### 0.1.9.0
- Expanded Crossed OW to four separate options, see Readme for details
@@ -160,7 +232,7 @@
- Fixed issues with Link/Bunny state in Crossed OW
- Fixed issue with Standard+Parallel not using vanilla connections for Escape
- Fixed issue with Mystery for OW boolean options
-- ~~Merged DR v0.5.1.0 - Major Keylogic Update~~
+- ~Merged DR v0.5.1.0 - Major Keylogic Update~
### 0.1.8.1
- Fixed issue with activating flute in DW (OW Mixed)
@@ -175,12 +247,12 @@
- Added OW Shuffle support for Plando module (needs user testing)
- Fixed issue with Sanc start at TR as bunny when it is LW
- Fixed issue with Pyramid Hole not getting shuffled
-- ~~Merged DR v0.5.0.3 - Minor DR fixes~~
+- ~Merged DR v0.5.0.3 - Minor DR fixes~
### 0.1.7.4
- Fixed issue with Mixed OW failing to generate when HC/Pyramid is swapped
- Various fixes to improve generation rates for Mixed OW Shuffle
-- ~~Merged DR v0.5.0.2 - Shuffle SFX~~
+- ~Merged DR v0.5.0.2 - Shuffle SFX~
### 0.1.7.3
- Fixed minor issue with ambient SFX stopping and starting on OW screen load
@@ -200,10 +272,10 @@
### 0.1.7.0
- Expanded new DR bomb logic to all modes (bomb usage in logic only if there is an unlimited supply of bombs available)
-- ~~Merged DR v0.5.0.1 - Bombbag mode / Enemizer fixes~~
+- ~Merged DR v0.5.0.1 - Bombbag mode / Enemizer fixes~
### 0.1.6.9
-- ~~Merged DR v0.4.0.12 - Secure random update / Credits fix~~
+- ~Merged DR v0.4.0.12 - Secure random update / Credits fix~
### 0.1.6.8
- Implemented a smarter Balanced Flute Shuffle algorithm
@@ -217,14 +289,14 @@
- Fixed Boss Music when boss room is entered thru straight stairs
- Suppressed in-dungeon music changes when DR is enabled
- Fixed issue with Pyramid Exit exiting to wrong location in ER
-- ~~Merged DR v0.4.0.11 - Various DR changes~~
+- ~Merged DR v0.4.0.11 - Various DR changes~
### 0.1.6.6
-- ~~Merged DR v0.4.0.9 - P/C Indicator / Credits fix / CLI Hints Fix~~
+- ~Merged DR v0.4.0.9 - P/C Indicator / Credits fix / CLI Hints Fix~
### 0.1.6.5
- Reduced chance of diagonal flute spot in Balanced
-- ~~Merged DR v0.4.0.8 - Boss Indicator / Psuedo Boots / Quickswap Update / Credits Updates~~
+- ~Merged DR v0.4.0.8 - Boss Indicator / Psuedo Boots / Quickswap Update / Credits Updates~
### 0.1.6.4
- Fixed Frogsmith and Stumpy and restored progression in these locations
@@ -263,15 +335,15 @@
### 0.1.5.0
- Added OW Tile Swap setting
- Fixed horizontal VRAM visual loading glitch on megatiles
-- ~~Merged DR v0.4.0.7 - Fast Credits / Reduced Flashing / Sprite Author in Credits~~
+- ~~Merged DR v0.4.0.7 - Fast Credits / Reduced Flashing / Sprite Author in Credits~~ Didn't fully merge
### 0.1.4.3
-- Merged DR v0.4.0.6 - TT Maiden Attic Hint / DR Entrance Floor Mat Mods / Hard/Expert Item Pool Fix
+- ~Merged DR v0.4.0.6 - TT Maiden Attic Hint / DR Entrance Floor Mat Mods / Hard/Expert Item Pool Fix~
### 0.1.4.2
- Modified various OW map terrain specific to OW Shuffle
- Changed World check to table-based vs OW ID-based (should have no effect with current modes)
-- Merged DR v0.4.0.5 - Mystery Boss Shuffle Fix / Swordless+Hard Item Pool Fix / Insanity+Inverted ER Fixes
+- ~Merged DR v0.4.0.5 - Mystery Boss Shuffle Fix / Swordless+Hard Item Pool Fix / Insanity+Inverted ER Fixes~
### 0.1.4.1
- Moved Inverted Pyramid Entrance to top of HC Ledge
@@ -285,11 +357,11 @@
- Various logic fixes and region prep for Inverted
- Fixed muted MSU-1 music in door rando when descending GT Climb stairs
- Fixed Standard + Vanilla (thanks compiling)
-- Merged DR v0.4.0.4 - Shuffle Link's House / Experimental Bunny Start / 10 Bomb Fix
+- ~Merged DR v0.4.0.4 - Shuffle Link's House / Experimental Bunny Start / 10 Bomb Fix~
### 0.1.3.0
- Added OWG Logic for OW Shuffle
-- Merged DR v0.4.0.2 - OWG Framework / YAML
+- ~Merged DR v0.4.0.2 - OWG Framework / YAML~
### 0.1.2.2
- Re-purposed OW Shuffle setting to Layout Shuffle
@@ -298,7 +370,7 @@
### 0.1.2.1
- Made possible fix for Standard
-- Merged DR v0.3.1.10 - Fixed Standard generation
+- ~Merged DR v0.3.1.10 - Fixed Standard generation~
### 0.1.2.0
- Added 'Parallel Worlds' toggle option
@@ -308,7 +380,7 @@
### 0.1.1.2
- If Link's current position fits within the incoming gap, Link will not get re-centered to the incoming gap
- Added Rule for Pearl required to drop down back of SW
-- Merged DR v0.3.1.8 - Improved Shopsanity pricing - Fixed Retro generation
+- ~Merged DR v0.3.1.8 - Improved Shopsanity pricing - Fixed Retro generation~
### 0.1.1.1
- Fixed camera unlocking issue
diff --git a/CLI.py b/CLI.py
index a1e53a3c..6be584e7 100644
--- a/CLI.py
+++ b/CLI.py
@@ -88,6 +88,10 @@ def parse_cli(argv, no_defaults=False):
if ret.keysanity:
ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4
+ if ret.keydropshuffle:
+ ret.dropshuffle = True
+ ret.pottery = 'keys' if ret.pottery == 'none' else ret.pottery
+
if multiargs.multi:
defaults = copy.deepcopy(ret)
for player in range(1, multiargs.multi + 1):
@@ -97,14 +101,15 @@ def parse_cli(argv, no_defaults=False):
'ow_shuffle', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle',
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'ganon_item', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
- 'bombbag', 'shuffleganon',
+ 'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items',
'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'pseudoboots',
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
- 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
- 'remote_items', 'shopsanity', 'keydropshuffle', 'mixed_travel', 'standardize_palettes', 'code',
- 'reduce_flashing', 'shuffle_sfx']:
+ 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
+ 'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
+ 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx',
+ 'msu_resume', 'collection_rate', 'colorizepots', 'bonk_drops']:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1:
setattr(ret, name, {1: value})
@@ -142,9 +147,12 @@ def parse_settings():
"progressive": "on",
"accessibility": "items",
"algorithm": "balanced",
+ 'mystery': False,
+ 'suppress_meta': False,
+ "restrict_boss_items": "none",
# Shuffle Ganon defaults to TRUE
- "openpyramid": False,
+ "openpyramid": "auto",
"shuffleganon": True,
"ow_shuffle": "vanilla",
"ow_crossed": "none",
@@ -152,11 +160,12 @@ def parse_settings():
"ow_mixed": False,
"ow_whirlpool": False,
"ow_fluteshuffle": "vanilla",
+ "bonk_drops": False,
"shuffle": "vanilla",
"shufflelinks": False,
+ "overworld_map": "default",
"pseudoboots": False,
- "shufflepots": False,
"shuffleenemies": "none",
"shufflebosses": "none",
"enemy_damage": "default",
@@ -164,7 +173,11 @@ def parse_settings():
"enemizercli": os.path.join(".", "EnemizerCLI", "EnemizerCLI.Core"),
"shopsanity": False,
- "keydropshuffle": False,
+ 'keydropshuffle': False,
+ 'dropshuffle': False,
+ 'pottery': 'none',
+ 'colorizepots': False,
+ 'shufflepots': False,
"mapshuffle": False,
"compassshuffle": False,
"keyshuffle": False,
@@ -201,6 +214,8 @@ def parse_settings():
"uw_palettes": "default",
"reduce_flashing": False,
"shuffle_sfx": False,
+ 'msu_resume': False,
+ 'collection_rate': False,
# Spoiler defaults to TRUE
# Playthrough defaults to TRUE
@@ -208,9 +223,11 @@ def parse_settings():
"create_spoiler": True,
"calc_playthrough": True,
"create_rom": True,
+ "bps": False,
"usestartinventory": False,
"custom": False,
"rom": os.path.join(".", "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"),
+ "patch": os.path.join(".", "Patch File.bps"),
"seed": "",
"count": 1,
diff --git a/DoorShuffle.py b/DoorShuffle.py
index 45ecbc7b..145ff35d 100644
--- a/DoorShuffle.py
+++ b/DoorShuffle.py
@@ -6,6 +6,7 @@ from enum import unique, Flag
from typing import DefaultDict, Dict, List
from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo, dungeon_keys
+from BaseClasses import PotFlags, LocationType
from Doors import reset_portals
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
from Dungeons import dungeon_bigs, dungeon_hints
@@ -14,6 +15,7 @@ from RoomData import DoorKind, PairedDoor, reset_rooms
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths, drop_entrances
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
from DungeonGenerator import dungeon_portals, dungeon_drops, GenerationException
+from DungeonGenerator import valid_region_to_explore as valid_region_to_explore_lim
from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock
from Utils import ncr, kth_combination
@@ -44,10 +46,10 @@ def link_doors(world, player):
reset_rooms(world, player)
world.get_door("Skull Pinball WS", player).no_exit()
world.swamp_patch_required[player] = orig_swamp_patch
+ link_doors_prep(world, player)
-def link_doors_main(world, player):
-
+def link_doors_prep(world, player):
# Drop-down connections & push blocks
for exitName, regionName in logical_connections:
connect_simple_door(world, exitName, regionName, player)
@@ -100,6 +102,7 @@ def link_doors_main(world, player):
analyze_portals(world, player)
for portal in world.dungeon_portals[player]:
connect_portal(portal, world, player)
+
if not world.doorShuffle[player] == 'vanilla':
fix_big_key_doors_with_ugly_smalls(world, player)
else:
@@ -120,11 +123,14 @@ def link_doors_main(world, player):
for ent, ext in default_one_way_connections:
connect_one_way(world, ent, ext, player)
vanilla_key_logic(world, player)
- elif world.doorShuffle[player] == 'basic':
+
+
+def link_doors_main(world, player):
+ if world.doorShuffle[player] == 'basic':
within_dungeon(world, player)
elif world.doorShuffle[player] == 'crossed':
cross_dungeon(world, player)
- else:
+ elif world.doorShuffle[player] != 'vanilla':
logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player])
raise Exception('Invalid door shuffle setting: %s' % world.doorShuffle[player])
@@ -209,17 +215,22 @@ def vanilla_key_logic(world, player):
key_layout = build_key_layout(builder, start_regions, doors, world, player)
valid = validate_key_layout(key_layout, world, player)
if not valid:
- logging.getLogger('').warning('Vanilla key layout not valid %s', builder.name)
+ logging.getLogger('').info('Vanilla key layout not valid %s', builder.name)
builder.key_door_proposal = doors
if player not in world.key_logic.keys():
world.key_logic[player] = {}
analyze_dungeon(key_layout, world, player)
world.key_logic[player][builder.name] = key_layout.key_logic
+ world.key_layout[player][builder.name] = key_layout
log_key_logic(builder.name, key_layout.key_logic)
# if world.shuffle[player] == 'vanilla' and world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'none' and not world.owMixed[player] and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]:
# validate_vanilla_key_logic(world, player)
+def validate_vanilla_reservation(dungeon, world, player):
+ return validate_key_layout(world.key_layout[player][dungeon.name], world, player)
+
+
# some useful functions
oppositemap = {
Direction.South: Direction.North,
@@ -370,7 +381,7 @@ def choose_portals(world, player):
if world.doorShuffle[player] in ['basic', 'crossed']:
cross_flag = world.doorShuffle[player] == 'crossed'
# key drops allow the big key in the right place in Desert Tiles 2
- bk_shuffle = world.bigkeyshuffle[player] or world.keydropshuffle[player]
+ bk_shuffle = world.bigkeyshuffle[player] or world.pottery[player] not in ['none', 'cave']
std_flag = world.mode[player] == 'standard'
# roast incognito doors
world.get_room(0x60, player).delete(5)
@@ -404,7 +415,7 @@ def choose_portals(world, player):
info.sole_entrance = inaccessible_portals[0]
info.required_passage.clear()
else:
- raise Exception('please inspect this case')
+ raise Exception(f'No reachable entrances for {dungeon}')
if len(reachable_portals) == 1:
info.sole_entrance = reachable_portals[0]
info_map[dungeon] = info
@@ -511,7 +522,7 @@ def analyze_portals(world, player):
info.sole_entrance = inaccessible_portals[0]
info.required_passage.clear()
else:
- raise Exception('please inspect this case')
+ raise Exception(f'No reachable entrances for {dungeon}')
if len(reachable_portals) == 1:
info.sole_entrance = reachable_portals[0]
if world.intensity[player] < 2 and world.doorShuffle[player] == 'basic' and dungeon == 'Desert Palace':
@@ -903,7 +914,7 @@ def enable_new_entrances(region, connections, potentials, enabled, world, player
def inverted_aga_check(entrances_map, connections, potentials, enabled, world, player):
- if world.mode[player] == 'inverted':
+ if world.is_atgt_swapped(player):
if 'Agahnims Tower' in entrances_map.keys() or aga_tower_enabled(enabled):
for region in list(potentials.keys()):
if region.name == 'Hyrule Castle Ledge':
@@ -978,18 +989,28 @@ def cross_dungeon(world, player):
paths = determine_required_paths(world, player)
check_required_paths(paths, world, player)
+ hc_compass = ItemFactory('Compass (Escape)', player)
+ at_compass = ItemFactory('Compass (Agahnims Tower)', player)
+ at_map = ItemFactory('Map (Agahnims Tower)', player)
+ if world.restrict_boss_items[player] != 'none':
+ hc_compass.advancement = at_compass.advancement = at_map.advancement = True
hc = world.get_dungeon('Hyrule Castle', player)
- hc.dungeon_items.append(ItemFactory('Compass (Escape)', player))
+ hc.dungeon_items.append(hc_compass)
at = world.get_dungeon('Agahnims Tower', player)
- at.dungeon_items.append(ItemFactory('Compass (Agahnims Tower)', player))
- at.dungeon_items.append(ItemFactory('Map (Agahnims Tower)', player))
+ at.dungeon_items.append(at_compass)
+ at.dungeon_items.append(at_map)
assign_cross_keys(dungeon_builders, world, player)
all_dungeon_items_cnt = len(list(y for x in world.dungeons if x.player == player for y in x.all_items))
- if world.keydropshuffle[player]:
- target_items = 35 if world.retro[player] else 96
+ target_items = 34
+ if world.retro[player]:
+ target_items += 1 if world.dropshuffle[player] else 0 # the hc big key
else:
- target_items = 34 if world.retro[player] else 63
+ target_items += 29 # small keys in chests
+ if world.dropshuffle[player]:
+ target_items += 14 # 13 dropped smalls + 1 big
+ if world.pottery[player] not in ['none', 'cave']:
+ target_items += 19 # 19 pot keys
d_items = target_items - all_dungeon_items_cnt
world.pool_adjustment[player] = d_items
smooth_door_pairs(world, player)
@@ -1047,7 +1068,11 @@ def assign_cross_keys(dungeon_builders, world, player):
logging.getLogger('').info(world.fish.translate("cli", "cli", "shuffling.keydoors"))
start = time.process_time()
if world.retro[player]:
- remaining = 61 if world.keydropshuffle[player] else 29
+ remaining = 29
+ if world.dropshuffle[player]:
+ remaining += 13
+ if world.pottery[player] not in ['none', 'cave']:
+ remaining += 19
else:
remaining = len(list(x for dgn in world.dungeons if dgn.player == player for x in dgn.small_keys))
total_keys = remaining
@@ -1066,11 +1091,10 @@ def assign_cross_keys(dungeon_builders, world, player):
total_candidates += builder.key_doors_num
start_regions_map[name] = start_regions
-
# Step 2: Initial Key Number Assignment & Calculate Flexibility
for name, builder in dungeon_builders.items():
calculated = int(round(builder.key_doors_num*total_keys/total_candidates))
- max_keys = builder.location_cnt - calc_used_dungeon_items(builder)
+ max_keys = max(0, builder.location_cnt - calc_used_dungeon_items(builder))
cand_len = max(0, len(builder.candidates) - builder.key_drop_cnt)
limit = min(max_keys, cand_len)
suggested = min(calculated, limit)
@@ -1243,7 +1267,13 @@ def refine_hints(dungeon_builders):
for region in builder.master_sector.regions:
for location in region.locations:
if not location.event and '- Boss' not in location.name and '- Prize' not in location.name and location.name != 'Sanctuary':
- location.hint_text = dungeon_hints[name]
+ if location.type == LocationType.Pot and location.pot:
+ hint_text = ('under a block' if location.pot.flags & PotFlags.Block else 'in a pot')
+ location.hint_text = f'{hint_text} {dungeon_hints[name]}'
+ elif location.type == LocationType.Drop:
+ location.hint_text = f'dropped {dungeon_hints[name]}'
+ else:
+ location.hint_text = dungeon_hints[name]
def refine_boss_exits(world, player):
@@ -1279,6 +1309,7 @@ def refine_boss_exits(world, player):
if 0 < len(filtered) < len(reachable_portals):
reachable_portals = filtered
chosen_one = random.choice(reachable_portals) if len(reachable_portals) > 1 else reachable_portals[0]
+ chosen_one.chosen = True
if chosen_one != current_boss:
chosen_one.change_boss_exit(current_boss.boss_exit_idx)
current_boss.change_boss_exit(-1)
@@ -1369,6 +1400,8 @@ def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
dungeon_builders[recombine.name] = recombine
+# todo: this allows cross-dungeon exploring via HC Ledge or Inaccessible Regions
+# todo: @deprecated
def valid_region_to_explore(region, world, player):
return region and (region.type == RegionType.Dungeon
or region.name in world.inaccessible_regions[player]
@@ -1560,7 +1593,7 @@ okay_normals = [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, DoorKind.
def find_key_door_candidates(region, checked, world, player):
- dungeon = region.dungeon
+ dungeon_name = region.dungeon.name
candidates = []
checked_doors = list(checked)
queue = deque([(region, None, None)])
@@ -1570,14 +1603,16 @@ def find_key_door_candidates(region, checked, world, player):
d = ext.door
if d and d.controller:
d = d.controller
- if d and not d.blocked and not d.entranceFlag and d.dest is not last_door and d.dest is not last_region and d not in checked_doors:
+ if d and not d.blocked and d.dest is not last_door and d.dest is not last_region and d not in checked_doors:
valid = False
- if 0 <= d.doorListPos < 4 and d.type in [DoorType.Interior, DoorType.Normal, DoorType.SpiralStairs]:
+ if (0 <= d.doorListPos < 4 and d.type in [DoorType.Interior, DoorType.Normal, DoorType.SpiralStairs]
+ and not d.entranceFlag):
room = world.get_room(d.roomIndex, player)
position, kind = room.doorList[d.doorListPos]
-
if d.type == DoorType.Interior:
valid = kind in [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, DoorKind.Dashable]
+ if valid and d.dest not in candidates: # interior doors are not separable yet
+ candidates.append(d.dest)
elif d.type == DoorType.SpiralStairs:
valid = kind in [DoorKind.StairKey, DoorKind.StairKey2, DoorKind.StairKeyLow]
elif d.type == DoorType.Normal:
@@ -1596,7 +1631,7 @@ def find_key_door_candidates(region, checked, world, player):
if valid and d not in candidates:
candidates.append(d)
connected = ext.connected_region
- if connected and (connected.type != RegionType.Dungeon or connected.dungeon == dungeon):
+ if valid_region_to_explore_lim(connected, dungeon_name, world, player):
queue.append((ext.connected_region, d, current))
if d is not None:
checked_doors.append(d)
@@ -1714,7 +1749,7 @@ def smooth_door_pairs(world, player):
if type_b == DoorKind.SmallKey:
remove_pair(door, world, player)
else:
- if valid_pair:
+ if valid_pair and not std_forbidden(door, world, player):
bd_candidates[door.entrance.parent_region.dungeon].append(door)
elif type_a in [DoorKind.Bombable, DoorKind.Dashable] or type_b in [DoorKind.Bombable, DoorKind.Dashable]:
if type_a in [DoorKind.Bombable, DoorKind.Dashable]:
@@ -1723,7 +1758,8 @@ def smooth_door_pairs(world, player):
else:
room_b.change(partner.doorListPos, DoorKind.Normal)
remove_pair(partner, world, player)
- elif valid_pair and type_a != DoorKind.SmallKey and type_b != DoorKind.SmallKey:
+ elif (valid_pair and type_a != DoorKind.SmallKey and type_b != DoorKind.SmallKey
+ and not std_forbidden(door, world, player)):
bd_candidates[door.entrance.parent_region.dungeon].append(door)
shuffle_bombable_dashable(bd_candidates, world, player)
world.paired_doors[player] = [x for x in world.paired_doors[player] if x.pair or x.original]
@@ -1762,12 +1798,37 @@ def stateful_door(door, kind):
return False
+def std_forbidden(door, world, player):
+ return (world.mode[player] == 'standard' and door.entrance.parent_region.dungeon.name == 'Hyrule Castle' and
+ 'Hyrule Castle Throne Room N' in [door.name, door.dest.name])
+
+
+dashable_forbidden = {
+ 'Swamp Trench 1 Key Ledge NW', 'Swamp Left Elbow WN', 'Swamp Right Elbow SE', 'Mire Hub WN', 'Mire Hub WS',
+ 'Mire Hub Top NW', 'Mire Hub NE', 'Ice Dead End WS'
+}
+
+ohko_forbidden = {
+ 'GT Invisible Catwalk NE', 'GT Falling Bridge WN', 'GT Falling Bridge WS', 'GT Hidden Star ES', 'GT Hookshot EN',
+ 'GT Torch Cross WN', 'TR Torches WN', 'Mire Falling Bridge WS', 'Mire Falling Bridge W', 'Ice Hookshot Balcony SW',
+ 'Ice Catwalk WN', 'Ice Catwalk NW', 'Ice Bomb Jump NW', 'GT Cannonball Bridge SE'
+}
+
+
+def filter_dashable_candidates(candidates, world):
+ forbidden_set = dashable_forbidden
+ if world.timer in ['ohko', 'timed-ohko']:
+ forbidden_set = ohko_forbidden.union(dashable_forbidden)
+ return [x for x in candidates if x.name not in forbidden_set and x.dest.name not in forbidden_set]
+
+
def shuffle_bombable_dashable(bd_candidates, world, player):
if world.doorShuffle[player] == 'basic':
for dungeon, candidates in bd_candidates.items():
diff = bomb_dash_counts[dungeon.name][1]
if diff > 0:
- for chosen in random.sample(candidates, min(diff, len(candidates))):
+ dash_candidates = filter_dashable_candidates(candidates, world)
+ for chosen in random.sample(dash_candidates, min(diff, len(candidates))):
change_pair_type(chosen, DoorKind.Dashable, world, player)
candidates.remove(chosen)
diff = bomb_dash_counts[dungeon.name][0]
@@ -1779,7 +1840,8 @@ def shuffle_bombable_dashable(bd_candidates, world, player):
remove_pair_type_if_present(excluded, world, player)
elif world.doorShuffle[player] == 'crossed':
all_candidates = sum(bd_candidates.values(), [])
- for chosen in random.sample(all_candidates, min(8, len(all_candidates))):
+ dash_candidates = filter_dashable_candidates(all_candidates, world)
+ for chosen in random.sample(dash_candidates, min(8, len(all_candidates))):
change_pair_type(chosen, DoorKind.Dashable, world, player)
all_candidates.remove(chosen)
for chosen in random.sample(all_candidates, min(12, len(all_candidates))):
@@ -1823,7 +1885,7 @@ def find_inaccessible_regions(world, player):
while len(queue) > 0:
next_region = queue.popleft()
visited_regions.add(next_region)
- if next_region.name == 'Dark Sanctuary Hint': # special spawn point in cave
+ if world.mode[player] == 'inverted' and next_region.name == 'Dark Sanctuary Hint': # special spawn point in cave
for ent in next_region.entrances:
parent = ent.parent_region
if parent and parent.type is not RegionType.Dungeon and parent not in queue and parent not in visited_regions:
@@ -1839,16 +1901,18 @@ def find_inaccessible_regions(world, player):
if any(x for x in ledge.exits if x.connected_region and x.connected_region.name == 'Agahnims Tower Portal'):
world.inaccessible_regions[player].append('Hyrule Castle Ledge')
logger = logging.getLogger('')
- logger.debug('Inaccessible Regions:')
- for r in world.inaccessible_regions[player]:
- logger.debug('%s', r)
+ #logger.debug('Inaccessible Regions:')
+ #for r in world.inaccessible_regions[player]:
+ # logger.debug('%s', r)
def find_accessible_entrances(world, player, builder):
entrances = [region.name for region in (portal.door.entrance.parent_region for portal in world.dungeon_portals[player]) if region.dungeon.name == builder.name]
entrances.extend(drop_entrances[builder.name])
+ hc_std = False
if world.mode[player] == 'standard' and builder.name == 'Hyrule Castle':
+ hc_std = True
start_regions = ['Hyrule Castle Courtyard']
else:
start_regions = ['Links House' if not world.is_bombshop_start(player) else 'Big Bomb Shop', 'Sanctuary' if world.mode[player] != 'inverted' else 'Dark Sanctuary Hint']
@@ -1873,6 +1937,8 @@ def find_accessible_entrances(world, player, builder):
if connect not in queue and connect not in visited_regions:
queue.append(connect)
for ext in next_region.exits:
+ if hc_std and ext.name == 'Hyrule Castle Main Gate (North)': # just skip it
+ continue
connect = ext.connected_region
if connect is None or ext.door and ext.door.blocked:
continue
@@ -2098,6 +2164,7 @@ logical_connections = [
('Hera Startile Wide Crystal Exit', 'Hera Startile Wide'),
('Hera Big Chest Hook Path', 'Hera Big Chest Landing'),
('Hera Big Chest Landing Exit', 'Hera 4F'),
+ ('Hera 5F Orange Path', 'Hera 5F Pot Block'),
('PoD Pit Room Block Path N', 'PoD Pit Room Blocked'),
('PoD Pit Room Block Path S', 'PoD Pit Room'),
@@ -2161,6 +2228,7 @@ logical_connections = [
('Swamp Trench 1 Departure Approach', 'Swamp Trench 1 Approach'),
('Swamp Trench 1 Departure Key', 'Swamp Trench 1 Key Ledge'),
('Swamp Hub Hook Path', 'Swamp Hub North Ledge'),
+ ('Swamp Hub Side Hook Path', 'Swamp Hub Side Ledges'),
('Swamp Hub North Ledge Drop Down', 'Swamp Hub'),
('Swamp Crystal Switch Outer to Inner Barrier - Blue', 'Swamp Crystal Switch Inner'),
('Swamp Crystal Switch Outer to Ranged Crystal', 'Swamp Crystal Switch Outer - Ranged Crystal'),
@@ -2192,13 +2260,17 @@ logical_connections = [
('Skull Pot Circle Star Path', 'Skull Map Room'),
('Skull Big Chest Hookpath', 'Skull 1 Lobby'),
('Skull Back Drop Star Path', 'Skull Small Hall'),
+ ('Skull 2 West Lobby Pits', 'Skull 2 West Lobby Ledge'),
+ ('Skull 2 West Lobby Ledge Pits', 'Skull 2 West Lobby'),
('Thieves Rail Ledge Drop Down', 'Thieves BK Corner'),
('Thieves Hellway Orange Barrier', 'Thieves Hellway S Crystal'),
('Thieves Hellway Crystal Orange Barrier', 'Thieves Hellway'),
('Thieves Hellway Blue Barrier', 'Thieves Hellway N Crystal'),
('Thieves Hellway Crystal Blue Barrier', 'Thieves Hellway'),
('Thieves Attic Orange Barrier', 'Thieves Attic Hint'),
+ ('Thieves Attic Blue Barrier', 'Thieves Attic Switch'),
('Thieves Attic Hint Orange Barrier', 'Thieves Attic'),
+ ('Thieves Attic Switch Blue Barrier', 'Thieves Attic'),
('Thieves Basement Block Path', 'Thieves Blocked Entry'),
('Thieves Blocked Entry Path', 'Thieves Basement Block'),
('Thieves Conveyor Bridge Block Path', 'Thieves Conveyor Block'),
@@ -2257,6 +2329,8 @@ logical_connections = [
('TR Main Lobby Gap', 'TR Lobby Ledge'),
('TR Lobby Ledge Gap', 'TR Main Lobby'),
+ ('TR Hub Path', 'TR Hub Ledges'),
+ ('TR Hub Ledges Path', 'TR Hub'),
('TR Pipe Ledge Drop Down', 'TR Pipe Pit'),
('TR Big Chest Gap', 'TR Big Chest Entrance'),
('TR Big Chest Entrance Gap', 'TR Big Chest'),
@@ -2285,6 +2359,8 @@ logical_connections = [
('TR Crystaroller Chest to Middle Barrier - Blue', 'TR Crystaroller Middle'),
('TR Crystaroller Middle Ranged Crystal Exit', 'TR Crystaroller Middle'),
('TR Crystaroller Bottom Ranged Crystal Exit', 'TR Crystaroller Bottom'),
+ ('TR Dark Ride Path', 'TR Dark Ride Ledges'),
+ ('TR Dark Ride Ledges Path', 'TR Dark Ride'),
('TR Crystal Maze Start to Interior Barrier - Blue', 'TR Crystal Maze Interior'),
('TR Crystal Maze Start to Crystal', 'TR Crystal Maze Start - Crystal'),
('TR Crystal Maze Start Crystal Exit', 'TR Crystal Maze Start'),
@@ -2295,16 +2371,18 @@ logical_connections = [
('TR Crystal Maze End to Interior Barrier - Blue', 'TR Crystal Maze Interior'),
('TR Crystal Maze End to Ranged Crystal', 'TR Crystal Maze End - Ranged Crystal'),
('TR Crystal Maze End Ranged Crystal Exit', 'TR Crystal Maze End'),
+ ('TR Final Abyss Balcony Path', 'TR Final Abyss Ledge'),
+ ('TR Final Abyss Ledge Path', 'TR Final Abyss Balcony'),
('GT Blocked Stairs Block Path', 'GT Big Chest'),
('GT Speed Torch South Path', 'GT Speed Torch'),
('GT Speed Torch North Path', 'GT Speed Torch Upper'),
- ('GT Hookshot East-North Path', 'GT Hookshot North Platform'),
- ('GT Hookshot East-South Path', 'GT Hookshot South Platform'),
- ('GT Hookshot North-East Path', 'GT Hookshot East Platform'),
- ('GT Hookshot North-South Path', 'GT Hookshot South Platform'),
- ('GT Hookshot South-East Path', 'GT Hookshot East Platform'),
- ('GT Hookshot South-North Path', 'GT Hookshot North Platform'),
+ ('GT Hookshot East-Mid Path', 'GT Hookshot Mid Platform'),
+ ('GT Hookshot Mid-East Path', 'GT Hookshot East Platform'),
+ ('GT Hookshot North-Mid Path', 'GT Hookshot Mid Platform'),
+ ('GT Hookshot Mid-North Path', 'GT Hookshot North Platform'),
+ ('GT Hookshot South-Mid Path', 'GT Hookshot Mid Platform'),
+ ('GT Hookshot Mid-South Path', 'GT Hookshot South Platform'),
('GT Hookshot Platform Blue Barrier', 'GT Hookshot South Entry'),
('GT Hookshot Platform Barrier Bypass', 'GT Hookshot South Entry'),
('GT Hookshot Entry Blue Barrier', 'GT Hookshot South Platform'),
@@ -2335,8 +2413,8 @@ logical_connections = [
('GT Crystal Conveyor to Corner Barrier - Blue', 'GT Crystal Conveyor Corner'),
('GT Crystal Conveyor to Ranged Crystal', 'GT Crystal Conveyor - Ranged Crystal'),
('GT Crystal Conveyor Corner to Left Bypass', 'GT Crystal Conveyor Left'),
- ('GT Crystal Conveyor Corner to Barrier - Blue', 'GT Crystal Conveyor Left'),
- ('GT Crystal Conveyor Corner to Barrier - Orange', 'GT Crystal Conveyor'),
+ ('GT Crystal Conveyor Corner to Barrier - Blue', 'GT Crystal Conveyor'),
+ ('GT Crystal Conveyor Corner to Barrier - Orange', 'GT Crystal Conveyor Left'),
('GT Crystal Conveyor Corner to Ranged Crystal', 'GT Crystal Conveyor Corner - Ranged Crystal'),
('GT Crystal Conveyor Left to Corner Barrier - Orange', 'GT Crystal Conveyor Corner'),
('GT Crystal Conveyor Ranged Crystal Exit', 'GT Crystal Conveyor'),
@@ -3002,7 +3080,8 @@ palette_map = {
'Tower of Hera': (0x6, None),
'Thieves Town': (0x17, None), # the attic uses 0x23
'Turtle Rock': (0x18, 0x19, 'TR Boss SW'),
- 'Ganons Tower': (0x28, 0x1b, 'GT Agahnim 2 SW'), # other palettes: 0x1a (other) 0x24 (Gauntlet - Lanmo) 0x25 (conveyor-torch-wizzrode moldorm pit f5?)
+ 'Ganons Tower': (0x28, 0x1b, 'GT Agahnim 2 SW'),
+ # other palettes: 0x1a (other) 0x24 (Gauntlet - Lanmo) 0x25 (conveyor-torch-wizzrobe moldorm pit f5?)
}
# implications:
diff --git a/Doors.py b/Doors.py
index a23f46f0..a7c1d9f3 100644
--- a/Doors.py
+++ b/Doors.py
@@ -309,6 +309,7 @@ def create_doors(world, player):
create_door(player, 'Hera 5F Star Hole', Hole),
create_door(player, 'Hera 5F Pothole Chain', Hole),
create_door(player, 'Hera 5F Normal Holes', Hole),
+ create_door(player, 'Hera 5F Orange Path', Lgcl),
create_door(player, 'Hera Fairies\' Warp', Warp),
create_door(player, 'Hera Boss Down Stairs', Sprl).dir(Dn, 0x07, 0, HTH).ss(S, 0x61, 0xb0).kill(),
create_door(player, 'Hera Boss Outer Hole', Hole),
@@ -434,7 +435,7 @@ def create_doors(world, player):
create_door(player, 'PoD Dark Basement W Up Stairs', Sprl).dir(Up, 0x6a, 0, HTH).ss(S, 0x1b, 0x3c, True),
create_door(player, 'PoD Dark Basement E Up Stairs', Sprl).dir(Up, 0x6a, 1, HTH).ss(S, 0x1b, 0x9c, True),
create_door(player, 'PoD Dark Alley NE', Nrml).dir(No, 0x6a, Right, High).big_key().pos(0),
- create_door(player, 'PoD Mimics 2 SW', Nrml).dir(So, 0x1b, Left, High).pos(1).kill().portal(Z, 0x00),
+ create_door(player, 'PoD Mimics 2 SW', Nrml).dir(So, 0x1b, Left, High).pos(1).portal(Z, 0x00),
create_door(player, 'PoD Mimics 2 NW', Intr).dir(No, 0x1b, Left, High).pos(0),
create_door(player, 'PoD Bow Statue SW', Intr).dir(So, 0x1b, Left, High).pos(0),
create_door(player, 'PoD Bow Statue Left to Right Barrier - Orange', Lgcl),
@@ -500,6 +501,7 @@ def create_doors(world, player):
create_door(player, 'Swamp Hub WS', Nrml).dir(We, 0x36, Bot, High).pos(3),
create_door(player, 'Swamp Hub WN', Nrml).dir(We, 0x36, Top, High).small_key().pos(2),
create_door(player, 'Swamp Hub Hook Path', Lgcl),
+ create_door(player, 'Swamp Hub Side Hook Path', Lgcl),
create_door(player, 'Swamp Hub Dead Ledge EN', Nrml).dir(Ea, 0x36, Top, High).pos(0),
create_door(player, 'Swamp Hub North Ledge N', Nrml).dir(No, 0x36, Mid, High).small_key().pos(1),
create_door(player, 'Swamp Hub North Ledge Drop Down', Lgcl),
@@ -612,6 +614,8 @@ def create_doors(world, player):
create_door(player, 'Skull 2 West Lobby S', Nrml).dir(So, 0x56, Left, High).pos(1).portal(Z, 0x00),
create_door(player, 'Skull 2 West Lobby ES', Intr).dir(Ea, 0x56, Bot, High).pos(2),
create_door(player, 'Skull 2 West Lobby NW', Intr).dir(No, 0x56, Left, High).small_key().pos(0),
+ create_door(player, 'Skull 2 West Lobby Pits', Lgcl),
+ create_door(player, 'Skull 2 West Lobby Ledge Pits', Lgcl),
create_door(player, 'Skull X Room SW', Intr).dir(So, 0x56, Left, High).small_key().pos(0),
create_door(player, 'Skull Back Drop Star Path', Lgcl),
create_door(player, 'Skull 3 Lobby SW', Nrml).dir(So, 0x59, Left, High).pos(1).portal(Z, 0x02),
@@ -684,7 +688,9 @@ def create_doors(world, player):
create_door(player, 'Thieves Attic Down Stairs', Sprl).dir(Dn, 0x64, 0, HTH).ss(Z, 0x11, 0x80, True, True),
create_door(player, 'Thieves Attic ES', Intr).dir(Ea, 0x64, Bot, High).pos(0),
create_door(player, 'Thieves Attic Orange Barrier', Lgcl),
+ create_door(player, 'Thieves Attic Blue Barrier', Lgcl),
create_door(player, 'Thieves Attic Hint Orange Barrier', Lgcl),
+ create_door(player, 'Thieves Attic Switch Blue Barrier', Lgcl),
create_door(player, 'Thieves Cricket Hall Left WS', Intr).dir(We, 0x64, Bot, High).pos(0),
create_door(player, 'Thieves Cricket Hall Left Edge', Open).dir(Ea, 0x64, None, High).edge(0, X, 0x30),
create_door(player, 'Thieves Cricket Hall Right Edge', Open).dir(We, 0x65, None, High).edge(0, Z, 0x30),
@@ -955,6 +961,8 @@ def create_doors(world, player):
create_door(player, 'TR Hub EN', Nrml).dir(Ea, 0xc6, Top, High).pos(2),
create_door(player, 'TR Hub NW', Nrml).dir(No, 0xc6, Left, High).small_key().pos(0),
create_door(player, 'TR Hub NE', Nrml).dir(No, 0xc6, Right, High).pos(1),
+ create_door(player, 'TR Hub Path', Lgcl),
+ create_door(player, 'TR Hub Ledges Path', Lgcl),
create_door(player, 'TR Torches Ledge WS', Nrml).dir(We, 0xc7, Bot, High).pos(2),
create_door(player, 'TR Torches WN', Nrml).dir(We, 0xc7, Top, High).pos(1),
create_door(player, 'TR Torches NW', Nrml).dir(No, 0xc7, Left, High).trap(0x4).pos(0),
@@ -1030,6 +1038,8 @@ def create_doors(world, player):
create_door(player, 'TR Crystaroller Down Stairs', Sprl).dir(Dn, 0x04, 0, HTH).ss(A, 0x12, 0x80, True, True).small_key().pos(0),
create_door(player, 'TR Dark Ride Up Stairs', Sprl).dir(Up, 0xb5, 0, HTH).ss(A, 0x1b, 0x6c),
create_door(player, 'TR Dark Ride SW', Nrml).dir(So, 0xb5, Left, High).trap(0x4).pos(0).portal(Z, 0x22),
+ create_door(player, 'TR Dark Ride Path', Lgcl),
+ create_door(player, 'TR Dark Ride Ledges Path', Lgcl),
create_door(player, 'TR Dash Bridge NW', Nrml).dir(No, 0xc5, Left, High).pos(1),
create_door(player, 'TR Dash Bridge SW', Nrml).dir(So, 0xc5, Left, High).pos(2).portal(Z, 0x02),
create_door(player, 'TR Dash Bridge WS', Nrml).dir(We, 0xc5, Bot, High).small_key().pos(0),
@@ -1048,6 +1058,8 @@ def create_doors(world, player):
create_door(player, 'TR Crystal Maze End Ranged Crystal Exit', Lgcl),
create_door(player, 'TR Crystal Maze North Stairs', StrS).dir(No, 0xc4, Mid, High),
create_door(player, 'TR Final Abyss South Stairs', StrS).dir(So, 0xb4, Mid, High),
+ create_door(player, 'TR Final Abyss Balcony Path', Lgcl),
+ create_door(player, 'TR Final Abyss Ledge Path', Lgcl),
create_door(player, 'TR Final Abyss NW', Nrml).dir(No, 0xb4, Left, High).big_key().pos(0),
create_door(player, 'TR Boss SW', Nrml).dir(So, 0xa4, Left, High).no_exit().trap(0x4).pos(0), # .portal(Z, 0x00), -enemizer doesn't work
@@ -1098,12 +1110,12 @@ def create_doors(world, player):
create_door(player, 'GT Conveyor Cross EN', Nrml).dir(Ea, 0x8b, Top, High).pos(2),
create_door(player, 'GT Conveyor Cross WN', Intr).dir(We, 0x8b, Top, High).pos(0),
create_door(player, 'GT Hookshot EN', Intr).dir(Ea, 0x8b, Top, High).pos(0),
- create_door(player, 'GT Hookshot East-North Path', Lgcl),
- create_door(player, 'GT Hookshot East-South Path', Lgcl),
- create_door(player, 'GT Hookshot North-East Path', Lgcl),
- create_door(player, 'GT Hookshot North-South Path', Lgcl),
- create_door(player, 'GT Hookshot South-East Path', Lgcl),
- create_door(player, 'GT Hookshot South-North Path', Lgcl),
+ create_door(player, 'GT Hookshot East-Mid Path', Lgcl),
+ create_door(player, 'GT Hookshot Mid-East Path', Lgcl),
+ create_door(player, 'GT Hookshot North-Mid Path', Lgcl),
+ create_door(player, 'GT Hookshot Mid-North Path', Lgcl),
+ create_door(player, 'GT Hookshot South-Mid Path', Lgcl),
+ create_door(player, 'GT Hookshot Mid-South Path', Lgcl),
create_door(player, 'GT Hookshot Platform Blue Barrier', Lgcl),
create_door(player, 'GT Hookshot Entry Blue Barrier', Lgcl),
create_door(player, 'GT Hookshot Platform Barrier Bypass', Lgcl),
@@ -1291,6 +1303,7 @@ def create_doors(world, player):
world.get_door('Hera Beetles Holes Front', player).c_switch()
world.get_door('Hera Beetles Holes Landing', player).c_switch()
world.get_door('Hera Startile Wide Crystal Exit', player).c_switch()
+ world.get_door('Hera 5F Orange Path', player).barrier(CrystalBarrier.Orange)
world.get_door('PoD Arena North to Landing Barrier - Orange', player).barrier(CrystalBarrier.Orange)
world.get_door('PoD Arena Main to Landing Barrier - Blue', player).barrier(CrystalBarrier.Blue)
@@ -1345,7 +1358,9 @@ def create_doors(world, player):
world.get_door('Thieves Hellway Orange Barrier', player).barrier(CrystalBarrier.Orange)
world.get_door('Thieves Hellway Crystal Orange Barrier', player).barrier(CrystalBarrier.Orange)
world.get_door('Thieves Attic Orange Barrier', player).barrier(CrystalBarrier.Orange)
+ world.get_door('Thieves Attic Blue Barrier', player).barrier(CrystalBarrier.Blue)
world.get_door('Thieves Attic Hint Orange Barrier', player).barrier(CrystalBarrier.Orange)
+ world.get_door('Thieves Attic Switch Blue Barrier', player).barrier(CrystalBarrier.Blue)
world.get_door('Ice Bomb Drop SE', player).c_switch()
world.get_door('Ice Conveyor Crystal Exit', player).c_switch()
@@ -1421,8 +1436,7 @@ def create_doors(world, player):
world.get_door('GT Crystal Conveyor Ranged Crystal Exit', player).c_switch()
world.get_door('GT Crystal Conveyor Corner Ranged Crystal Exit', player).c_switch()
world.get_door('GT Crystal Conveyor Corner to Left Bypass', player).barrier(CrystalBarrier.Blue)
- world.get_door('GT Hookshot South-North Path', player).c_switch()
- world.get_door('GT Hookshot South-East Path', player).c_switch()
+ world.get_door('GT Hookshot South-Mid Path', player).c_switch()
world.get_door('GT Hookshot ES', player).c_switch()
world.get_door('GT Hookshot Platform Barrier Bypass', player).barrier(CrystalBarrier.Orange)
world.get_door('GT Hookshot Platform Blue Barrier', player).barrier(CrystalBarrier.Blue)
@@ -1453,6 +1467,11 @@ def create_doors(world, player):
world.get_door('GT Spike Crystal Right to Left Barrier - Orange', player).barrier(CrystalBarrier.Orange)
world.get_door('GT Spike Crystal Left to Right Bypass', player).barrier(CrystalBarrier.Blue)
+ # kill certain doors
+ if world.intensity[player] == 1: # due to ladder & warp being fixed
+ world.get_door('PoD Mimics 2 SW', player).kill()
+
+
# nifty dynamic logical doors:
south_controller = world.get_door('Ice Cross Bottom SE', player)
east_controller = world.get_door('Ice Cross Right ES', player)
diff --git a/DungeonGenerator.py b/DungeonGenerator.py
index f88499bd..f0038f45 100644
--- a/DungeonGenerator.py
+++ b/DungeonGenerator.py
@@ -218,7 +218,8 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, all_regions, pro
return name == 'Skull Woods 2' and d.name == 'Skull Pinball WS'
original_state = extend_reachable_state_improved(entrance_regions, start, proposed_map, all_regions,
valid_doors, bk_flag, world, player, exception)
- dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map, exception)
+ dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map, exception,
+ world, player)
either_crystal = True # if all hooks from the origin are either, explore all bits with either
for hook, crystal in dungeon['Origin'].hooks.items():
if crystal != CrystalBarrier.Either:
@@ -239,7 +240,7 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, all_regions, pro
o_state = extend_reachable_state_improved([parent], init_state, proposed_map, all_regions,
valid_doors, bk_flag, world, player, exception)
o_state_cache[door.name] = o_state
- piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map, exception)
+ piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map, exception, world, player)
dungeon[door.name] = piece
check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, all_regions, valid_doors,
group_flags, door_map, world, player, exception)
@@ -339,7 +340,7 @@ def explore_blue_state(door, dungeon, o_state, proposed_map, all_regions, valid_
blue_start.big_key_special = o_state.big_key_special
b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, all_regions, valid_doors, bk_flag,
world, player, exception)
- dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception)
+ dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception, world, player)
def make_a_choice(dungeon, hangers, avail_hooks, prev_choices, name):
@@ -603,7 +604,7 @@ def winnow_hangers(hangers, hooks):
hangers[hanger].remove(door)
-def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception):
+def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception, world, player):
# todo: info about dungeon events - not sure about that
graph_piece = GraphPiece()
all_unattached = {}
@@ -635,16 +636,15 @@ def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exceptio
graph_piece.visited_regions.update(o_state.visited_orange)
graph_piece.visited_regions.update(b_state.visited_blue)
graph_piece.visited_regions.update(b_state.visited_orange)
- graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(o_state.bk_found))
- graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(b_state.bk_found))
+ graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(o_state.bk_found, world, player))
+ graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(b_state.bk_found, world, player))
graph_piece.pinball_used = o_state.pinball_used or b_state.pinball_used
return graph_piece
-def filter_for_potential_bk_locations(locations):
- return [x for x in locations if
- '- Big Chest' not in x.name and '- Prize' not in x.name and x.name not in dungeon_events
- and not x.forced_item and x.name not in ['Agahnim 1', 'Agahnim 2']]
+def filter_for_potential_bk_locations(locations, world, player):
+ return [x for x in locations if '- Big Chest' not in x.name and not reserved_location(x, world, player) and
+ not x.forced_item and not prize_or_event(x) and not blind_boss_unavail(x, locations, world, player)]
type_map = {
@@ -987,12 +987,8 @@ class ExplorationState(object):
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
return True
- def count_locations_exclude_specials(self):
- cnt = 0
- for loc in self.found_locations:
- if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events and not loc.forced_item:
- cnt += 1
- return cnt
+ def count_locations_exclude_specials(self, world, player):
+ return count_locations_exclude_big_chest(self.found_locations, world, player)
def validate(self, door, region, world, player):
return self.can_traverse(door) and not self.visited(region) and valid_region_to_explore(region, self.dungeon,
@@ -1033,6 +1029,32 @@ class ExplorationState(object):
return 2
+def count_locations_exclude_big_chest(locations, world, player):
+ cnt = 0
+ for loc in locations:
+ if ('- Big Chest' not in loc.name and not loc.forced_item and not reserved_location(loc, world, player)
+ and not prize_or_event(loc) and not blind_boss_unavail(loc, locations, world, player)):
+ cnt += 1
+ return cnt
+
+
+def prize_or_event(loc):
+ return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
+
+
+def reserved_location(loc, world, player):
+ return hasattr(world, 'item_pool_config') and loc.name in world.item_pool_config.reserved_locations[player]
+
+
+def blind_boss_unavail(loc, locations, world, player):
+ if loc.name == "Thieves' Town - Boss":
+ return (loc.parent_region.dungeon.boss.name == 'Blind' and
+ (not any(x for x in locations if x.name == 'Suspicious Maiden') or
+ (world.get_region('Thieves Attic Window', player).dungeon.name == 'Thieves Town' and
+ not any(x for x in locations if x.name == 'Attic Cracked Floor'))))
+ return False
+
+
class ExplorableDoor(object):
def __init__(self, door, crystal, flag):
@@ -1056,7 +1078,8 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, all_reg
explorable_door = local_state.next_avail_door()
if explorable_door.door.bigKey:
if bk_flag:
- big_not_found = not special_big_key_found(local_state) if local_state.big_key_special else local_state.count_locations_exclude_specials() == 0
+ big_not_found = (not special_big_key_found(local_state) if local_state.big_key_special
+ else local_state.count_locations_exclude_specials(world, player) == 0)
if big_not_found:
continue # we can't open this door
if explorable_door.door in proposed_map:
@@ -1139,6 +1162,8 @@ class DungeonBuilder(object):
self.sectors = []
self.location_cnt = 0
self.key_drop_cnt = 0
+ self.dungeon_items = None # during fill how many dungeon items are left
+ self.free_items = None # during fill how many dungeon items are left
self.bk_required = False
self.bk_provided = False
self.c_switch_required = False
@@ -1301,7 +1326,17 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
polarized_sectors[sector] = None
if bow_sectors:
assign_bow_sectors(dungeon_map, bow_sectors, global_pole)
- assign_location_sectors(dungeon_map, free_location_sectors, global_pole)
+ leftover = assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_pole, world, player)
+ free_location_sectors = scatter_extra_location_sectors(dungeon_map, leftover, global_pole)
+ for sector in free_location_sectors:
+ if sector.c_switch:
+ crystal_switches[sector] = None
+ elif sector.blue_barrier:
+ crystal_barriers[sector] = None
+ elif sector.polarity().is_neutral():
+ neutral_sectors[sector] = None
+ else:
+ polarized_sectors[sector] = None
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole)
ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole)
for sector in leftover:
@@ -1452,6 +1487,7 @@ def define_sector_features(sectors):
sector.bk_provided = True
elif loc.name not in dungeon_events and not loc.forced_item:
sector.chest_locations += 1
+ sector.chest_location_set.add(loc.name)
if '- Big Chest' in loc.name or loc.name in ["Hyrule Castle - Zelda's Chest",
"Thieves' Town - Blind's Cell"]:
sector.bk_required = True
@@ -1532,26 +1568,85 @@ def assign_bow_sectors(dungeon_map, bow_sectors, global_pole):
assign_sector(sector_list[i], builder, bow_sectors, global_pole)
-def assign_location_sectors(dungeon_map, free_location_sectors, global_pole):
+def scatter_extra_location_sectors(dungeon_map, free_location_sectors, global_pole):
+ population = [n for n in dungeon_map.keys()]
+ k = round(len(free_location_sectors) * .50)
valid = False
choices = None
+ candidates = []
+ sector_list = list(free_location_sectors)
+ while not valid:
+ candidates = random.sample(sector_list, k=k)
+ choices = random.choices(population, k=len(candidates))
+ sector_dict = defaultdict(list)
+ for i, choice in enumerate(choices):
+ builder = dungeon_map[choice]
+ sector_dict[builder].append(candidates[i])
+ valid = global_pole.is_valid_multi_choice_2(dungeon_map, dungeon_map.values(), sector_dict)
+ for i, choice in enumerate(choices):
+ builder = dungeon_map[choice]
+ assign_sector(candidates[i], builder, free_location_sectors, global_pole)
+ return free_location_sectors
+
+
+def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_pole, world, player):
+ valid = False
+ choices = defaultdict(list)
sector_list = list(free_location_sectors)
random.shuffle(sector_list)
+ orig_location_set = build_orig_location_set(dungeon_map)
+ num_dungeon_items = requested_dungeon_items(world, player)
+ d_idx = {builder.name: i for i, builder in enumerate(dungeon_map.values())}
+ next_sector = sector_list.pop()
while not valid:
- choices, d_idx, totals = weighted_random_locations(dungeon_map, sector_list)
- for i, sector in enumerate(sector_list):
- choice = d_idx[choices[i].name]
- totals[choice] += sector.chest_locations
- valid = True
- for d_name, idx in d_idx.items():
- if totals[idx] < 5: # min locations for dungeons is 5 (bk exception)
- valid = False
- break
- for i, choice in enumerate(choices):
- builder = dungeon_map[choice.name]
- assign_sector(sector_list[i], builder, free_location_sectors, global_pole)
+ choice, totals, location_set = weighted_random_location(dungeon_map, choices, orig_location_set, world, player)
+ if not choice:
+ break
+ choices[choice].append(next_sector)
+ if global_pole.is_valid_multi_choice_2(dungeon_map, dungeon_map.values(), choices):
+ idx = d_idx[choice.name]
+ totals[idx] += next_sector.chest_locations
+ location_set[choice.name].update(next_sector.chest_location_set)
+ valid = True
+ for d_name, idx in d_idx.items():
+ free_items = count_reserved_locations(world, player, location_set[d_name])
+ target = max(free_items, 2) + num_dungeon_items
+ if totals[idx] < target:
+ valid = False
+ break
+ if not valid:
+ if len(sector_list) == 0:
+ choices = defaultdict(list)
+ sector_list = list(free_location_sectors)
+ else:
+ next_sector = sector_list.pop()
+ else:
+ choices[choice].remove(next_sector)
+ for builder, choice_list in choices.items():
+ for choice in choice_list:
+ assign_sector(choice, builder, free_location_sectors, global_pole)
+ return free_location_sectors
+def weighted_random_location(dungeon_map, choices, orig_location_set, world, player):
+ population = []
+ totals = []
+ location_set = {x: set(y) for x, y in orig_location_set.items()}
+ num_dungeon_items = requested_dungeon_items(world, player)
+ for i, dungeon_builder in enumerate(dungeon_map.values()):
+ ttl = dungeon_builder.location_cnt + sum(sector.chest_locations for sector in choices[dungeon_builder])
+ totals.append(ttl)
+ builder_set = location_set[dungeon_builder.name]
+ builder_set.update(set().union(*(s.chest_location_set for s in choices[dungeon_builder])))
+ free_items = count_reserved_locations(world, player, builder_set)
+ target = max(free_items, 2) + num_dungeon_items
+ if ttl < target:
+ population.append(dungeon_builder)
+ choice = random.choice(population) if len(population) > 0 else None
+ return choice, totals, location_set
+
+
+# deprecated
def weighted_random_locations(dungeon_map, free_location_sectors):
population = []
ttl_assigned = 0
@@ -1575,6 +1670,30 @@ def weighted_random_locations(dungeon_map, free_location_sectors):
return choices, d_idx, totals
+def build_orig_location_set(dungeon_map):
+ orig_locations = {}
+ for name, builder in dungeon_map.items():
+ orig_locations[name] = set().union(*(s.chest_location_set for s in builder.sectors))
+ return orig_locations
+
+
+def requested_dungeon_items(world, player):
+ num = 0
+ if not world.bigkeyshuffle[player]:
+ num += 1
+ if not world.compassshuffle[player]:
+ num += 1
+ if not world.mapshuffle[player]:
+ num += 1
+ return num
+
+
+def count_reserved_locations(world, player, proposed_set):
+ if world.item_pool_config:
+ return len([x for x in proposed_set if x in world.item_pool_config.reserved_locations[player]])
+ return 2
+
+
def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole, assign_one=False):
population = []
some_c_switches_present = False
diff --git a/DungeonRandomizer.py b/DungeonRandomizer.py
index d24e81d6..cf0f73bc 100755
--- a/DungeonRandomizer.py
+++ b/DungeonRandomizer.py
@@ -1,11 +1,11 @@
#!/usr/bin/env python3
-import argparse
-import copy
+if __name__ == '__main__':
+ from source.meta.check_requirements import check_requirements
+ check_requirements(console=True)
+
import os
import logging
import RaceRandom as random
-import textwrap
-import shlex
import sys
from source.classes.BabelFish import BabelFish
diff --git a/Dungeons.py b/Dungeons.py
index 6fe38cfb..188cf59f 100644
--- a/Dungeons.py
+++ b/Dungeons.py
@@ -1,8 +1,5 @@
-import RaceRandom as random
-
from BaseClasses import Dungeon
from Bosses import BossFactory
-from Fill import fill_restrictive
from Items import ItemFactory
@@ -36,119 +33,6 @@ def create_dungeons(world, player):
world.dungeons += [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT]
-def fill_dungeons(world):
- freebes = ['Ganons Tower - Map Chest', 'Palace of Darkness - Harmless Hellway', 'Palace of Darkness - Big Key Chest', 'Turtle Rock - Big Key Chest']
-
- all_state_base = world.get_all_state()
-
- for player in range(1, world.players + 1):
- pinball_room = world.get_location('Skull Woods - Pinball Room', player)
- if world.retro[player]:
- world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
- else:
- world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
- pinball_room.event = True
- pinball_room.locked = True
-
- dungeons = [(list(dungeon.regions), dungeon.big_key, list(dungeon.small_keys), list(dungeon.dungeon_items)) for dungeon in world.dungeons]
-
- loopcnt = 0
- while dungeons:
- loopcnt += 1
- dungeon_regions, big_key, small_keys, dungeon_items = dungeons.pop(0)
- # this is what we need to fill
- dungeon_locations = [location for location in world.get_unfilled_locations() if location.parent_region.name in dungeon_regions]
- random.shuffle(dungeon_locations)
-
- all_state = all_state_base.copy()
-
- # first place big key
- if big_key is not None:
- bk_location = None
- for location in dungeon_locations:
- if location.item_rule(big_key):
- bk_location = location
- break
-
- if bk_location is None:
- raise RuntimeError('No suitable location for %s' % big_key)
-
- world.push_item(bk_location, big_key, False)
- bk_location.event = True
- bk_location.locked = True
- dungeon_locations.remove(bk_location)
- big_key = None
-
- # next place small keys
- while small_keys:
- small_key = small_keys.pop()
- all_state.sweep_for_events()
- sk_location = None
- for location in dungeon_locations:
- if location.name in freebes or (location.can_reach(all_state) and location.item_rule(small_key)):
- sk_location = location
- break
-
- if sk_location is None:
- # need to retry this later
- small_keys.append(small_key)
- dungeons.append((dungeon_regions, big_key, small_keys, dungeon_items))
- # infinite regression protection
- if loopcnt < (30 * world.players):
- break
- else:
- raise RuntimeError('No suitable location for %s' % small_key)
-
- world.push_item(sk_location, small_key, False)
- sk_location.event = True
- sk_location.locked = True
- dungeon_locations.remove(sk_location)
-
- if small_keys:
- # key placement not finished, loop again
- continue
-
- # next place dungeon items
- for dungeon_item in dungeon_items:
- di_location = dungeon_locations.pop()
- world.push_item(di_location, dungeon_item, False)
-
-
-def get_dungeon_item_pool(world):
- return [item for dungeon in world.dungeons for item in dungeon.all_items]
-
-def fill_dungeons_restrictive(world, shuffled_locations):
- all_state_base = world.get_all_state()
-
- # for player in range(1, world.players + 1):
- # pinball_room = world.get_location('Skull Woods - Pinball Room', player)
- # if world.retro[player]:
- # world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
- # else:
- # world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
- # pinball_room.event = True
- # pinball_room.locked = True
- # shuffled_locations.remove(pinball_room)
-
- # with shuffled dungeon items they are distributed as part of the normal item pool
- for item in world.get_items():
- if (item.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]):
- item.advancement = True
- elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]):
- item.priority = True
-
- dungeon_items = [item for item in get_dungeon_item_pool(world) if ((item.smallkey and not world.keyshuffle[item.player])
- or (item.bigkey and not world.bigkeyshuffle[item.player])
- or (item.map and not world.mapshuffle[item.player])
- or (item.compass and not world.compassshuffle[item.player]))]
-
- # sort in the order Big Key, Small Key, Other before placing dungeon items
- sort_order = {"BigKey": 3, "SmallKey": 2}
- dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
-
- fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items,
- keys_in_itempool={player: not world.keyshuffle[player] for player in range(1, world.players+1)}, single_player_placement=True)
-
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
'Desert Palace - Prize': [0x1559B, 0x1559C, 0x1559D, 0x1559E],
@@ -200,7 +84,7 @@ hera_regions = [
'Hera Back - Ranged Crystal', 'Hera Basement Cage', 'Hera Basement Cage - Crystal', 'Hera Tile Room',
'Hera Tridorm', 'Hera Tridorm - Crystal', 'Hera Torches', 'Hera Beetles', 'Hera Startile Corner',
'Hera Startile Wide', 'Hera Startile Wide - Crystal', 'Hera 4F', 'Hera Big Chest Landing', 'Hera 5F',
- 'Hera Fairies', 'Hera Boss', 'Hera Portal'
+ 'Hera 5F Pot Block', 'Hera Fairies', 'Hera Boss', 'Hera Portal'
]
tower_regions = [
@@ -226,24 +110,24 @@ pod_regions = [
swamp_regions = [
'Swamp Lobby', 'Swamp Entrance', 'Swamp Pot Row', 'Swamp Map Ledge', 'Swamp Trench 1 Approach',
'Swamp Trench 1 Nexus', 'Swamp Trench 1 Alcove', 'Swamp Trench 1 Key Ledge', 'Swamp Trench 1 Departure',
- 'Swamp Hammer Switch', 'Swamp Hub', 'Swamp Hub Dead Ledge', 'Swamp Hub North Ledge', 'Swamp Donut Top',
- 'Swamp Donut Bottom', 'Swamp Compass Donut', 'Swamp Crystal Switch Outer', 'Swamp Crystal Switch Outer - Ranged Crystal',
- 'Swamp Crystal Switch Inner', 'Swamp Crystal Switch Inner - Crystal', 'Swamp Shortcut', 'Swamp Trench 2 Pots',
- 'Swamp Trench 2 Blocks', 'Swamp Trench 2 Alcove', 'Swamp Trench 2 Departure', 'Swamp Big Key Ledge',
- 'Swamp West Shallows', 'Swamp West Block Path', 'Swamp West Ledge', 'Swamp Barrier Ledge', 'Swamp Barrier',
- 'Swamp Attic', 'Swamp Push Statue', 'Swamp Shooters', 'Swamp Left Elbow', 'Swamp Right Elbow', 'Swamp Drain Left',
- 'Swamp Drain Right', 'Swamp Flooded Room', 'Swamp Flooded Spot', 'Swamp Basement Shallows', 'Swamp Waterfall Room',
- 'Swamp Refill', 'Swamp Behind Waterfall', 'Swamp C', 'Swamp Waterway', 'Swamp I', 'Swamp T', 'Swamp Boss',
- 'Swamp Portal'
+ 'Swamp Hammer Switch', 'Swamp Hub', 'Swamp Hub Side Ledges', 'Swamp Hub Dead Ledge', 'Swamp Hub North Ledge',
+ 'Swamp Donut Top', 'Swamp Donut Bottom', 'Swamp Compass Donut', 'Swamp Crystal Switch Outer',
+ 'Swamp Crystal Switch Outer - Ranged Crystal', 'Swamp Crystal Switch Inner', 'Swamp Crystal Switch Inner - Crystal',
+ 'Swamp Shortcut', 'Swamp Trench 2 Pots', 'Swamp Trench 2 Blocks', 'Swamp Trench 2 Alcove',
+ 'Swamp Trench 2 Departure', 'Swamp Big Key Ledge', 'Swamp West Shallows', 'Swamp West Block Path',
+ 'Swamp West Ledge', 'Swamp Barrier Ledge', 'Swamp Barrier', 'Swamp Attic', 'Swamp Push Statue', 'Swamp Shooters',
+ 'Swamp Left Elbow', 'Swamp Right Elbow', 'Swamp Drain Left', 'Swamp Drain Right', 'Swamp Flooded Room',
+ 'Swamp Flooded Spot', 'Swamp Basement Shallows', 'Swamp Waterfall Room', 'Swamp Refill', 'Swamp Behind Waterfall',
+ 'Swamp C', 'Swamp Waterway', 'Swamp I', 'Swamp T', 'Swamp Boss', 'Swamp Portal'
]
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', 'Skull 1 Portal', 'Skull 2 East Portal',
- 'Skull 2 West Portal', 'Skull 3 Portal'
+ 'Skull Lone Pot', 'Skull Small Hall', 'Skull Back Drop', 'Skull 2 West Lobby', 'Skull 2 West Lobby Ledge',
+ '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',
+ 'Skull 1 Portal', 'Skull 2 East Portal', 'Skull 2 West Portal', 'Skull 3 Portal'
]
thieves_regions = [
@@ -251,10 +135,10 @@ thieves_regions = [
'Thieves Big Chest Nook', 'Thieves Hallway', 'Thieves Boss', 'Thieves Pot Alcove Mid', 'Thieves Pot Alcove Bottom',
'Thieves Pot Alcove Top', 'Thieves Conveyor Maze', 'Thieves Spike Track', 'Thieves Hellway',
'Thieves Hellway N Crystal', 'Thieves Hellway S Crystal', 'Thieves Triple Bypass', 'Thieves Spike Switch',
- 'Thieves Attic', 'Thieves Attic Hint', 'Thieves Cricket Hall Left', 'Thieves Cricket Hall Right',
- 'Thieves Attic Window', 'Thieves Basement Block', 'Thieves Blocked Entry', 'Thieves Lonely Zazak',
- "Thieves Blind's Cell", "Thieves Blind's Cell Interior", 'Thieves Conveyor Bridge', 'Thieves Conveyor Block',
- 'Thieves Big Chest Room', 'Thieves Trap', 'Thieves Town Portal'
+ 'Thieves Attic', 'Thieves Attic Hint', 'Thieves Attic Switch', 'Thieves Cricket Hall Left',
+ 'Thieves Cricket Hall Right', 'Thieves Attic Window', 'Thieves Basement Block', 'Thieves Blocked Entry',
+ 'Thieves Lonely Zazak', "Thieves Blind's Cell", "Thieves Blind's Cell Interior", 'Thieves Conveyor Bridge',
+ 'Thieves Conveyor Block', 'Thieves Big Chest Room', 'Thieves Trap', 'Thieves Town Portal'
]
ice_regions = [
@@ -284,29 +168,31 @@ mire_regions = [
]
tr_regions = [
- 'TR Main Lobby', 'TR Lobby Ledge', 'TR Compass Room', 'TR Hub', 'TR Torches Ledge', 'TR Torches', 'TR Roller Room',
- 'TR Tile Room', 'TR Refill', 'TR Pokey 1', 'TR Chain Chomps Top', 'TR Chain Chomps Top - Crystal',
- 'TR Chain Chomps Bottom', 'TR Chain Chomps Bottom - Ranged Crystal', 'TR Pipe Pit', 'TR Pipe Ledge', 'TR Lava Dual Pipes',
- 'TR Lava Island', 'TR Lava Escape', 'TR Pokey 2 Top', 'TR Pokey 2 Top - Crystal', 'TR Pokey 2 Bottom', 'TR Pokey 2 Bottom - Ranged Crystal',
- 'TR Twin Pokeys', 'TR Hallway', 'TR Dodgers', 'TR Big View','TR Big Chest', 'TR Big Chest Entrance',
- 'TR Lazy Eyes', 'TR Dash Room', 'TR Tongue Pull', 'TR Rupees', 'TR Crystaroller Bottom',
- 'TR Crystaroller Middle', 'TR Crystaroller Top', 'TR Crystaroller Top - Crystal', 'TR Crystaroller Chest',
- 'TR Crystaroller Middle - Ranged Crystal', 'TR Crystaroller Bottom - Ranged Crystal', 'TR Dark Ride', 'TR Dash Bridge', 'TR Eye Bridge',
+ 'TR Main Lobby', 'TR Lobby Ledge', 'TR Compass Room', 'TR Hub', 'TR Hub Ledges', 'TR Torches Ledge', 'TR Torches',
+ 'TR Roller Room', 'TR Tile Room', 'TR Refill', 'TR Pokey 1', 'TR Chain Chomps Top', 'TR Chain Chomps Top - Crystal',
+ 'TR Chain Chomps Bottom', 'TR Chain Chomps Bottom - Ranged Crystal', 'TR Pipe Pit', 'TR Pipe Ledge',
+ 'TR Lava Dual Pipes', 'TR Lava Island', 'TR Lava Escape', 'TR Pokey 2 Top', 'TR Pokey 2 Top - Crystal',
+ 'TR Pokey 2 Bottom', 'TR Pokey 2 Bottom - Ranged Crystal', 'TR Twin Pokeys', 'TR Hallway', 'TR Dodgers',
+ 'TR Big View', 'TR Big Chest', 'TR Big Chest Entrance', 'TR Lazy Eyes', 'TR Dash Room', 'TR Tongue Pull',
+ 'TR Rupees', 'TR Crystaroller Bottom', 'TR Crystaroller Middle', 'TR Crystaroller Top',
+ 'TR Crystaroller Top - Crystal', 'TR Crystaroller Chest', 'TR Crystaroller Middle - Ranged Crystal',
+ 'TR Crystaroller Bottom - Ranged Crystal', 'TR Dark Ride', 'TR Dark Ride Ledges', 'TR Dash Bridge', 'TR Eye Bridge',
'TR Crystal Maze Start', 'TR Crystal Maze Start - Crystal', 'TR Crystal Maze Interior', 'TR Crystal Maze End',
- 'TR Crystal Maze End - Ranged Crystal', 'TR Final Abyss', 'TR Boss', 'Turtle Rock Main Portal',
- 'Turtle Rock Lazy Eyes Portal', 'Turtle Rock Chest Portal', 'Turtle Rock Eye Bridge Portal'
+ 'TR Crystal Maze End - Ranged Crystal', 'TR Final Abyss Balcony', 'TR Final Abyss Ledge', 'TR Boss',
+ 'Turtle Rock Main Portal', 'Turtle Rock Lazy Eyes Portal', 'Turtle Rock Chest Portal',
+ 'Turtle Rock Eye Bridge Portal'
]
gt_regions = [
'GT Lobby', 'GT Bob\'s Torch', 'GT Hope Room', 'GT Big Chest', 'GT Blocked Stairs', 'GT Bob\'s Room',
'GT Tile Room', 'GT Speed Torch', 'GT Speed Torch Upper', 'GT Pots n Blocks', 'GT Crystal Conveyor',
'GT Crystal Conveyor Corner', 'GT Crystal Conveyor Left', 'GT Crystal Conveyor - Ranged Crystal',
- 'GT Crystal Conveyor Corner - Ranged Crystal',
- 'GT Compass Room', 'GT Invisible Bridges', 'GT Invisible Catwalk', 'GT Conveyor Cross', 'GT Hookshot East Platform',
- 'GT Hookshot North Platform', 'GT Hookshot South Platform', 'GT Hookshot South Entry', 'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room',
+ 'GT Crystal Conveyor Corner - Ranged Crystal', 'GT Compass Room', 'GT Invisible Bridges', 'GT Invisible Catwalk',
+ 'GT Conveyor Cross', 'GT Hookshot East Platform', 'GT Hookshot Mid Platform', 'GT Hookshot North Platform',
+ 'GT Hookshot South Platform', 'GT Hookshot South Entry', 'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room',
'GT Double Switch Entry', 'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners',
- 'GT Double Switch Left', 'GT Double Switch Left - Crystal',
- 'GT Double Switch Entry - Ranged Switches', 'GT Double Switch Exit', 'GT Spike Crystal Left',
+ 'GT Double Switch Left', 'GT Double Switch Left - Crystal', 'GT Double Switch Entry - Ranged Switches',
+ 'GT Double Switch Exit', 'GT Spike Crystal Left',
'GT Spike Crystal Right', 'GT Warp Maze - Left Section', 'GT Warp Maze - Mid Section',
'GT Warp Maze - Right Section', 'GT Warp Maze - Pit Section', 'GT Warp Maze - Pit Exit Warp Spot',
'GT Warp Maze Exit Section', 'GT Firesnake Room', 'GT Firesnake Room Ledge', 'GT Warp Maze - Rail Choice',
@@ -378,8 +264,8 @@ flexible_starts = {
class DungeonInfo:
- def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize=None):
- # todo reduce static maps ideas: prize, bk_name, sm_name, cmp_name, map_name):
+ def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize, midx):
+ # todo reduce static maps ideas: prize, bk_name, sm_name, cmp_name, map_name):
self.free_items = free
self.key_num = keys
self.bk_present = bk
@@ -389,21 +275,23 @@ class DungeonInfo:
self.key_drops = drops
self.prize = prize
+ self.map_index = midx
+
dungeon_table = {
- 'Hyrule Castle': DungeonInfo(6, 1, False, True, False, True, 3, None),
- 'Eastern Palace': DungeonInfo(3, 0, True, True, True, False, 2, 'Eastern Palace - Prize'),
- 'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, 'Desert Palace - Prize'),
- 'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, 'Tower of Hera - Prize'),
- 'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None),
- 'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, 'Palace of Darkness - Prize'),
- 'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, 'Swamp Palace - Prize'),
- 'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, 'Skull Woods - Prize'),
- 'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, "Thieves' Town - Prize"),
- 'Ice Palace': DungeonInfo(3, 2, True, True, True, False, 4, 'Ice Palace - Prize'),
- 'Misery Mire': DungeonInfo(2, 3, True, True, True, False, 3, 'Misery Mire - Prize'),
- 'Turtle Rock': DungeonInfo(5, 4, True, True, True, False, 2, 'Turtle Rock - Prize'),
- 'Ganons Tower': DungeonInfo(20, 4, True, True, True, False, 4, None),
+ 'Hyrule Castle': DungeonInfo(6, 1, False, True, False, True, 3, None, 0xc),
+ 'Eastern Palace': DungeonInfo(3, 0, True, True, True, False, 2, 'Eastern Palace - Prize', 0x0),
+ 'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, 'Desert Palace - Prize', 0x2),
+ 'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, 'Tower of Hera - Prize', 0x1),
+ 'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None, 0xb),
+ 'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, 'Palace of Darkness - Prize', 0x3),
+ 'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, 'Swamp Palace - Prize', 0x9),
+ 'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, 'Skull Woods - Prize', 0x4),
+ 'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, "Thieves' Town - Prize", 0x6),
+ 'Ice Palace': DungeonInfo(3, 2, True, True, True, False, 4, 'Ice Palace - Prize', 0x8),
+ 'Misery Mire': DungeonInfo(2, 3, True, True, True, False, 3, 'Misery Mire - Prize', 0x7),
+ 'Turtle Rock': DungeonInfo(5, 4, True, True, True, False, 2, 'Turtle Rock - Prize', 0x5),
+ 'Ganons Tower': DungeonInfo(20, 4, True, True, True, False, 4, None, 0xa),
}
@@ -439,7 +327,6 @@ dungeon_bigs = {
'Ganons Tower': 'Big Key (Ganons Tower)'
}
-
dungeon_hints = {
'Hyrule Castle': 'in Hyrule Castle',
'Eastern Palace': 'in Eastern Palace',
diff --git a/EntranceShuffle.py b/EntranceShuffle.py
index 46217d2e..d6c207f2 100644
--- a/EntranceShuffle.py
+++ b/EntranceShuffle.py
@@ -3,7 +3,9 @@ from collections import defaultdict, OrderedDict
import RaceRandom as random
from BaseClasses import CollectionState, RegionType
from OverworldShuffle import build_accessible_region_list
+from DoorShuffle import find_inaccessible_regions
from OWEdges import OWTileRegions
+from Utils import stack_size3a
entrance_pool = list()
exit_pool = list()
@@ -30,7 +32,7 @@ def link_entrances(world, player):
Cave_Three_Exits = Cave_Three_Exits_Base.copy()
from OverworldShuffle import build_sectors
- if not world.owsectors[player]:
+ if not world.owsectors[player] and world.shuffle[player] != 'vanilla':
world.owsectors[player] = build_sectors(world, player)
# modifications to lists
@@ -72,7 +74,7 @@ def link_entrances(world, player):
# if we do not shuffle, set default connections
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
- for entrancename, exitname in default_connections + drop_connections + default_item_connections + default_shop_connections:
+ for entrancename, exitname in default_connections + default_pot_connections + drop_connections + default_item_connections + default_shop_connections:
connect_logical(world, entrancename, exitname, player, exitname.endswith(' Exit'))
for entrancename, exitname in default_connector_connections + dropexit_connections:
connect_logical(world, entrancename, exitname, player, True)
@@ -340,7 +342,7 @@ def link_entrances(world, player):
# place remaining doors
connect_doors(world, list(entrance_pool), list(exit_pool), player)
elif world.shuffle[player] == 'lite':
- for entrancename, exitname in default_connections + ([] if world.shopsanity[player] else default_shop_connections):
+ for entrancename, exitname in default_connections + ([] if world.shopsanity[player] else default_shop_connections) + ([] if world.pottery[player] not in ['none', 'keys', 'dungeon'] else default_pot_connections):
connect_logical(world, entrancename, exitname, player, False)
if invFlag:
world.get_entrance('Dark Sanctuary Hint Exit', player).connect(world.get_entrance('Dark Sanctuary Hint', player).parent_region)
@@ -432,7 +434,7 @@ def link_entrances(world, player):
# place remaining doors
connect_doors(world, list(entrance_pool), list(exit_pool), player)
elif world.shuffle[player] == 'lean':
- for entrancename, exitname in default_connections + ([] if world.shopsanity[player] else default_shop_connections):
+ for entrancename, exitname in default_connections + ([] if world.shopsanity[player] else default_shop_connections) + ([] if world.pottery[player] not in ['none', 'keys', 'dungeon'] else default_pot_connections):
connect_logical(world, entrancename, exitname, player, False)
if invFlag:
world.get_entrance('Dark Sanctuary Hint Exit', player).connect(world.get_entrance('Dark Sanctuary Hint', player).parent_region)
@@ -830,8 +832,6 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player, must
"""This works inplace"""
random.shuffle(entrances)
random.shuffle(caves)
-
- from DoorShuffle import find_inaccessible_regions
used_caves = []
required_entrances = 0 # Number of entrances reserved for used_caves
@@ -1273,7 +1273,6 @@ def full_shuffle_dungeons(world, Dungeon_Exits, player):
dw_entrances.extend([e for e in dungeon_owid_map[owid][0] if e in entrance_pool])
# determine must-exit entrances
- from DoorShuffle import find_inaccessible_regions
find_inaccessible_regions(world, player)
lw_must_exit = list()
@@ -1355,7 +1354,7 @@ def place_links_house(world, player, ignore_list=[]):
else:
links_house_doors = [i for i in get_starting_entrances(world, player, world.shuffle[player] != 'insanity') if i in entrance_pool]
if world.shuffle[player] in ['lite', 'lean']:
- links_house_doors = [e for e in links_house_doors if e in list(zip(*(default_item_connections + (default_shop_connections if world.shopsanity[player] else []))))[0]]
+ links_house_doors = [e for e in links_house_doors if e in list(zip(*(default_item_connections + (default_shop_connections if world.shopsanity[player] else []) + (default_pot_connections if world.pottery[player] not in ['none', 'keys', 'dungeon'] else []))))[0]]
#TODO: Need to improve Links House placement to choose a better sector or eliminate entrances that are after ledge drops
links_house_doors = [e for e in links_house_doors if e not in ignore_list]
@@ -1403,7 +1402,7 @@ def place_blacksmith(world, links_house, player):
sanc_region = world.get_entrance('Sanctuary Exit', player).connected_region.name
blacksmith_doors = list(OrderedDict.fromkeys(blacksmith_doors + list(build_accessible_entrance_list(world, sanc_region, player, assumed_inventory, False, True, True))))
if world.shuffle[player] in ['lite', 'lean']:
- blacksmith_doors = [e for e in blacksmith_doors if e in list(zip(*(default_item_connections + (default_shop_connections if world.shopsanity[player] else []))))[0]]
+ blacksmith_doors = [e for e in blacksmith_doors if e in list(zip(*(default_item_connections + (default_shop_connections if world.shopsanity[player] else []) + (default_pot_connections if world.pottery[player] not in ['none', 'keys', 'dungeon'] else []))))[0]]
assert len(blacksmith_doors), 'No valid candidates to place Blacksmiths Hut'
blacksmith_hut = random.choice(blacksmith_doors)
@@ -1441,15 +1440,13 @@ def place_old_man(world, pool, player, ignore_list=[]):
def junk_fill_inaccessible(world, player):
- from Main import copy_world
- from DoorShuffle import find_inaccessible_regions
+ from Main import copy_world_limited
find_inaccessible_regions(world, player)
for p in range(1, world.players + 1):
world.key_logic[p] = {}
- base_world = copy_world(world)
+ base_world = copy_world_limited(world)
base_world.override_bomb_check = True
- world.key_logic = {}
# remove regions that have a dungeon entrance
accessible_regions = list()
@@ -1472,7 +1469,7 @@ def junk_fill_inaccessible(world, player):
if not exit.connected_region and exit.name in entrance_pool:
inaccessible_entrances.append(exit.name)
- junk_locations = [e for e in list(zip(*default_connections))[1] if e in exit_pool]
+ junk_locations = [e for e in list(zip(*(default_connections + ([] if world.pottery[player] not in ['none', 'keys', 'dungeon'] else default_pot_connections))))[1] if e in exit_pool]
random.shuffle(junk_locations)
for entrance in inaccessible_entrances:
connect_entrance(world, entrance, junk_locations.pop(), player)
@@ -1481,10 +1478,13 @@ def junk_fill_inaccessible(world, player):
def connect_inaccessible_regions(world, lw_entrances, dw_entrances, caves, player, ignore_list=[]):
invFlag = world.mode[player] == 'inverted'
+ if stack_size3a() > 500:
+ from DungeonGenerator import GenerationException
+ raise GenerationException(f'Infinite loop detected at \'connect_inaccessible_regions\'')
+
random.shuffle(lw_entrances)
random.shuffle(dw_entrances)
- from DoorShuffle import find_inaccessible_regions
find_inaccessible_regions(world, player)
# remove regions that have a dungeon entrance
@@ -1607,14 +1607,13 @@ def unbias_dungeons(Dungeon_Exits):
def build_accessible_entrance_list(world, start_region, player, assumed_inventory=[], cross_world=False, region_rules=True, exit_rules=True, include_one_ways=False):
- from Main import copy_world
+ from Main import copy_world_limited
from Items import ItemFactory
for p in range(1, world.players + 1):
world.key_logic[p] = {}
- base_world = copy_world(world)
+ base_world = copy_world_limited(world)
base_world.override_bomb_check = True
- world.key_logic = {}
connect_simple(base_world, 'Links House S&Q', start_region, player)
blank_state = CollectionState(base_world)
@@ -1663,7 +1662,7 @@ def get_starting_entrances(world, player, force_starting_world=True):
# get entrances from list of regions
entrances = list()
for region_name in regions:
- if world.shuffle[player] == 'simple' and region_name in OWTileRegions and OWTileRegions[region_name] in [0x03, 0x05, 0x07]:
+ if world.shuffle[player] == 'simple' and region_name in OWTileRegions.keys() and OWTileRegions[region_name] in [0x03, 0x05, 0x07]:
continue
region = world.get_region(region_name, player)
if not force_starting_world or region.type == (RegionType.LightWorld if not invFlag else RegionType.DarkWorld):
@@ -1705,7 +1704,7 @@ def get_distant_entrances(world, start_entrance, player):
# get entrances from remaining regions
candidates = list()
for region_name in [r for r in regions if r not in explored_regions]:
- if OWTileRegions[region_name] in [0x03, 0x05, 0x07]:
+ if region_name in OWTileRegions.keys() and OWTileRegions[region_name] in [0x03, 0x05, 0x07]:
continue
region = world.get_region(region_name, player)
for exit in region.exits:
@@ -1716,15 +1715,13 @@ def get_distant_entrances(world, start_entrance, player):
def can_reach(world, entrance_name, region_name, player):
- from Main import copy_world
+ from Main import copy_world_limited
from Items import ItemFactory
- from DoorShuffle import find_inaccessible_regions
for p in range(1, world.players + 1):
world.key_logic[p] = {}
- base_world = copy_world(world)
+ base_world = copy_world_limited(world)
base_world.override_bomb_check = True
- world.key_logic = {}
entrance = world.get_entrance(entrance_name, player)
connect_simple(base_world, 'Links House S&Q', entrance.parent_region.name, player)
@@ -2054,6 +2051,8 @@ mandatory_connections = [('Old Man S&Q', 'Old Man House'),
('Lost Woods Hideout (top to bottom)', 'Lost Woods Hideout (bottom)'),
('Lumberjack Tree (top to bottom)', 'Lumberjack Tree (bottom)'),
('Kakariko Well (top to bottom)', 'Kakariko Well (bottom)'),
+ ('Kakariko Well (top to back)', 'Kakariko Well (back)'),
+ ('Blinds Hideout N', 'Blinds Hideout (Top)'),
('Bat Cave Door', 'Bat Cave (left)'),
('Sewer Drop', 'Sewers Rat Path'),
('Old Man Cave Dropdown', 'Old Man Cave'),
@@ -2061,10 +2060,13 @@ mandatory_connections = [('Old Man S&Q', 'Old Man House'),
('Old Man House Back to Front', 'Old Man House'),
('Spectacle Rock Cave Drop', 'Spectacle Rock Cave (Bottom)'),
('Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave (Bottom)'),
+ ('Death Mountain Return Cave E', 'Death Mountain Return Cave (right)'),
+ ('Death Mountain Return Cave W', 'Death Mountain Return Cave (left)'),
('Spiral Cave (top to bottom)', 'Spiral Cave (Bottom)'),
('Light World Death Mountain Shop', 'Light World Death Mountain Shop'),
('Paradox Cave Push Block Reverse', 'Paradox Cave Chest Area'),
('Paradox Cave Push Block', 'Paradox Cave Front'),
+ ('Paradox Cave Chest Area NE', 'Paradox Cave Bomb Area'),
('Paradox Cave Bomb Jump', 'Paradox Cave'),
('Paradox Cave Drop', 'Paradox Cave Chest Area'),
('Fairy Ascension Cave Climb', 'Fairy Ascension Cave (Top)'),
@@ -2076,45 +2078,49 @@ mandatory_connections = [('Old Man S&Q', 'Old Man House'),
('Hookshot Cave Middle to Front', 'Hookshot Cave (Front)'),
('Hookshot Cave Middle to Back', 'Hookshot Cave (Back)'),
('Hookshot Cave Back to Middle', 'Hookshot Cave (Middle)'),
+ ('Hookshot Cave Bonk Path', 'Hookshot Cave (Bonk Islands)'),
+ ('Hookshot Cave Hook Path', 'Hookshot Cave (Hook Islands)'),
('Ganon Drop', 'Bottom of Pyramid')
]
# non-shuffled entrance links
-default_connections = [('Lumberjack House', 'Lumberjack House'),
- ('Bonk Fairy (Light)', 'Bonk Fairy (Light)'),
+default_connections = [('Bonk Fairy (Light)', 'Bonk Fairy (Light)'),
('Lake Hylia Fairy', 'Lake Hylia Healer Fairy'),
('Lake Hylia Fortune Teller', 'Lake Hylia Fortune Teller'),
('Light Hype Fairy', 'Swamp Healer Fairy'),
('Desert Fairy', 'Desert Healer Fairy'),
('Lost Woods Gamble', 'Lost Woods Gamble'),
('Fortune Teller (Light)', 'Fortune Teller (Light)'),
- ('Snitch Lady (East)', 'Snitch Lady (East)'),
- ('Snitch Lady (West)', 'Snitch Lady (West)'),
('Bush Covered House', 'Bush Covered House'),
- ('Tavern (Front)', 'Tavern (Front)'),
- ('Light World Bomb Hut', 'Light World Bomb Hut'),
('Long Fairy Cave', 'Long Fairy Cave'), # near East Light World Teleporter
('Good Bee Cave', 'Good Bee Cave'),
- ('20 Rupee Cave', '20 Rupee Cave'),
- ('50 Rupee Cave', '50 Rupee Cave'),
('Kakariko Gamble Game', 'Kakariko Gamble Game'),
- ('Hookshot Fairy', 'Hookshot Fairy'),
('East Dark World Hint', 'East Dark World Hint'),
- ('Palace of Darkness Hint', 'Palace of Darkness Hint'),
('Dark Lake Hylia Fairy', 'Dark Lake Hylia Healer Fairy'),
('Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Healer Fairy'),
- ('Dark Lake Hylia Ledge Spike Cave', 'Dark Lake Hylia Ledge Spike Cave'),
('Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Hint'),
('Bonk Fairy (Dark)', 'Bonk Fairy (Dark)'),
('Dark Sanctuary Hint', 'Dark Sanctuary Hint'),
('Fortune Teller (Dark)', 'Fortune Teller (Dark)'),
('Archery Game', 'Archery Game'),
- ('Dark Desert Hint', 'Dark Desert Hint'),
('Dark Desert Fairy', 'Dark Desert Healer Fairy'),
('Dark Death Mountain Fairy', 'Dark Death Mountain Healer Fairy'),
]
+default_pot_connections = [('Lumberjack House', 'Lumberjack House'),
+ ('Snitch Lady (East)', 'Snitch Lady (East)'),
+ ('Snitch Lady (West)', 'Snitch Lady (West)'),
+ ('Tavern (Front)', 'Tavern (Front)'),
+ ('Light World Bomb Hut', 'Light World Bomb Hut'),
+ ('20 Rupee Cave', '20 Rupee Cave'),
+ ('50 Rupee Cave', '50 Rupee Cave'),
+ ('Hookshot Fairy', 'Hookshot Fairy'),
+ ('Palace of Darkness Hint', 'Palace of Darkness Hint'),
+ ('Dark Lake Hylia Ledge Spike Cave', 'Dark Lake Hylia Ledge Spike Cave'),
+ ('Dark Desert Hint', 'Dark Desert Hint')
+ ]
+
default_connector_connections = [('Old Man Cave (West)', 'Old Man Cave Exit (West)'),
('Old Man Cave (East)', 'Old Man Cave Exit (East)'),
('Old Man House (Bottom)', 'Old Man House Exit (Bottom)'),
@@ -2296,9 +2302,9 @@ one_way_ledges = {
indirect_connections = {
'Turtle Rock Ledge': 'Turtle Rock',
- #'Big Bomb Shop': 'Pyramid Fairy',
+ 'Big Bomb Shop': 'Pyramid Crack',
#'East Dark World': 'Pyramid Fairy',
- 'Pyramid Area': 'Pyramid Fairy', # HC Ledge/Courtyard
+ 'Pyramid Area': 'Pyramid Crack', # HC Ledge/Courtyard
#'Dark Desert': 'Pyramid Fairy',
#'Misery Mire Area': 'Pyramid Fairy', # Desert/Checkerboard Ledge
#'West Dark World': 'Pyramid Fairy',
@@ -2611,3 +2617,135 @@ exit_ids = {'Links House Exit': (0x01, 0x00),
'Skull Pinball': 0x78,
'Skull Pot Circle': 0x76,
'Pyramid': 0x7B}
+
+ow_prize_table = {'Links House': (0x8b1, 0xb2d),
+ 'Desert Palace Entrance (South)': (0x108, 0xd70), 'Desert Palace Entrance (West)': (0x031, 0xca0),
+ 'Desert Palace Entrance (North)': (0x0e1, 0xba0), 'Desert Palace Entrance (East)': (0x191, 0xca0),
+ 'Eastern Palace': (0xf31, 0x620), 'Tower of Hera': (0x8D0, 0x080),
+ 'Hyrule Castle Entrance (South)': (0x820, 0x730), 'Hyrule Castle Entrance (West)': (0x740, 0x5D0),
+ 'Hyrule Castle Entrance (East)': (0x8f0, 0x5D0), 'Inverted Pyramid Entrance': (0x6C0, 0x5D0),
+ 'Agahnims Tower': (0x820, 0x5D0),
+ 'Thieves Town': (0x1d0, 0x780), 'Skull Woods First Section Door': (0x240, 0x280),
+ 'Skull Woods Second Section Door (East)': (0x1a0, 0x240),
+ 'Skull Woods Second Section Door (West)': (0x0c0, 0x1c0), 'Skull Woods Final Section': (0x082, 0x0b0),
+ 'Ice Palace': (0xca0, 0xda0),
+ 'Misery Mire': (0x100, 0xca0),
+ 'Palace of Darkness': (0xf40, 0x620), 'Swamp Palace': (0x759, 0xED0),
+ 'Turtle Rock': (0xf11, 0x103),
+ 'Dark Death Mountain Ledge (West)': (0xb80, 0x180),
+ 'Dark Death Mountain Ledge (East)': (0xc80, 0x180),
+ 'Turtle Rock Isolated Ledge Entrance': (0xc00, 0x240),
+ 'Hyrule Castle Secret Entrance Stairs': (0x8D0, 0x700),
+ 'Kakariko Well Cave': (0x060, 0x680),
+ 'Bat Cave Cave': (0x540, 0x8f0),
+ 'Elder House (East)': (0x2b0, 0x6a0),
+ 'Elder House (West)': (0x230, 0x6a0),
+ 'North Fairy Cave': (0xa80, 0x440),
+ 'Lost Woods Hideout Stump': (0x240, 0x280),
+ 'Lumberjack Tree Cave': (0x4e0, 0x004),
+ 'Two Brothers House (East)': (0x200, 0x0b60),
+ 'Two Brothers House (West)': (0x180, 0x0b60),
+ 'Sanctuary': (0x720, 0x4a0),
+ 'Old Man Cave (West)': (0x580, 0x2c0),
+ 'Old Man Cave (East)': (0x620, 0x2c0),
+ 'Old Man House (Bottom)': (0x720, 0x320),
+ 'Old Man House (Top)': (0x820, 0x220),
+ 'Death Mountain Return Cave (East)': (0x600, 0x220),
+ 'Death Mountain Return Cave (West)': (0x500, 0x1c0),
+ 'Spectacle Rock Cave Peak': (0x720, 0x0a0),
+ 'Spectacle Rock Cave': (0x790, 0x1a0),
+ 'Spectacle Rock Cave (Bottom)': (0x710, 0x0a0),
+ 'Paradox Cave (Bottom)': (0xd80, 0x180),
+ 'Paradox Cave (Middle)': (0xd80, 0x380),
+ 'Paradox Cave (Top)': (0xd80, 0x020),
+ 'Fairy Ascension Cave (Bottom)': (0xcc8, 0x2a0),
+ 'Fairy Ascension Cave (Top)': (0xc00, 0x240),
+ 'Spiral Cave': (0xb80, 0x180),
+ 'Spiral Cave (Bottom)': (0xb80, 0x2c0),
+ 'Bumper Cave (Bottom)': (0x580, 0x2c0),
+ 'Bumper Cave (Top)': (0x500, 0x1c0),
+ 'Superbunny Cave (Top)': (0xd80, 0x020),
+ 'Superbunny Cave (Bottom)': (0xd00, 0x180),
+ 'Hookshot Cave': (0xc80, 0x0c0),
+ 'Hookshot Cave Back Entrance': (0xcf0, 0x004),
+ 'Ganons Tower': (0x8D0, 0x080),
+ 'Pyramid Entrance': (0x640, 0x7c0),
+ 'Skull Woods First Section Hole (West)': None,
+ 'Skull Woods First Section Hole (East)': None,
+ 'Skull Woods First Section Hole (North)': None,
+ 'Skull Woods Second Section Hole': None,
+ 'Pyramid Hole': None,
+ 'Inverted Pyramid Hole': None,
+ 'Waterfall of Wishing': (0xe80, 0x280),
+ 'Dam': (0x759, 0xED0),
+ 'Blinds Hideout': (0x190, 0x6c0),
+ 'Hyrule Castle Secret Entrance Drop': None,
+ 'Bonk Fairy (Light)': (0x740, 0xa80),
+ 'Lake Hylia Fairy': (0xd40, 0x9f0),
+ 'Light Hype Fairy': (0x940, 0xc80),
+ 'Desert Fairy': (0x420, 0xe00),
+ 'Kings Grave': (0x920, 0x520),
+ 'Tavern North': None, # can't mark this one technically
+ 'Chicken House': (0x120, 0x880),
+ 'Aginahs Cave': (0x2e0, 0xd00),
+ 'Sahasrahlas Hut': (0xcf0, 0x6c0),
+ 'Cave Shop (Lake Hylia)': (0xbc0, 0xc00),
+ 'Capacity Upgrade': (0xca0, 0xda0),
+ 'Kakariko Well Drop': None,
+ 'Blacksmiths Hut': (0x4a0, 0x880),
+ 'Bat Cave Drop': None,
+ 'Sick Kids House': (0x220, 0x880),
+ 'North Fairy Cave Drop': None,
+ 'Lost Woods Gamble': (0x240, 0x080),
+ 'Fortune Teller (Light)': (0x2c0, 0x4c0),
+ 'Snitch Lady (East)': (0x310, 0x7a0),
+ 'Snitch Lady (West)': (0x800, 0x7a0),
+ 'Bush Covered House': (0x2e0, 0x880),
+ 'Tavern (Front)': (0x270, 0x980),
+ 'Light World Bomb Hut': (0x070, 0x980),
+ 'Kakariko Shop': (0x170, 0x980),
+ 'Lost Woods Hideout Drop': None,
+ 'Lumberjack Tree Tree': None,
+ 'Cave 45': (0x440, 0xca0), 'Graveyard Cave': (0x8f0, 0x430),
+ 'Checkerboard Cave': (0x260, 0xc00),
+ 'Mini Moldorm Cave': (0xa40, 0xe80),
+ 'Long Fairy Cave': (0xf60, 0xb00),
+ 'Good Bee Cave': (0xec0, 0xc00),
+ '20 Rupee Cave': (0xe80, 0xca0),
+ '50 Rupee Cave': (0x4d0, 0xed0),
+ 'Ice Rod Cave': (0xe00, 0xc00),
+ 'Bonk Rock Cave': (0x5f0, 0x460),
+ 'Library': (0x270, 0xaa0),
+ 'Potion Shop': (0xc80, 0x4c0),
+ 'Sanctuary Grave': None,
+ 'Hookshot Fairy': (0xd00, 0x180),
+ 'Pyramid Fairy': (0x740, 0x740),
+ 'East Dark World Hint': (0xf60, 0xb00),
+ 'Palace of Darkness Hint': (0xd60, 0x7c0),
+ 'Dark Lake Hylia Fairy': (0xd40, 0x9f0),
+ 'Dark Lake Hylia Ledge Fairy': (0xe00, 0xc00),
+ 'Dark Lake Hylia Ledge Spike Cave': (0xe80, 0xca0),
+ 'Dark Lake Hylia Ledge Hint': (0xec0, 0xc00),
+ 'Hype Cave': (0x940, 0xc80),
+ 'Bonk Fairy (Dark)': (0x740, 0xa80),
+ 'Brewery': (0x170, 0x980), 'C-Shaped House': (0x310, 0x7a0), 'Chest Game': (0x800, 0x7a0),
+ 'Dark World Hammer Peg Cave': (0x4c0, 0x940),
+ 'Red Shield Shop': (0x500, 0x680),
+ 'Dark Sanctuary Hint': (0x720, 0x4a0),
+ 'Fortune Teller (Dark)': (0x2c0, 0x4c0),
+ 'Dark World Shop': (0x2e0, 0x880),
+ 'Dark World Lumberjack Shop': (0x4e0, 0x0d0),
+ 'Dark World Potion Shop': (0xc80, 0x4c0),
+ 'Archery Game': (0x2f0, 0xaf0),
+ 'Mire Shed': (0x060, 0xc90),
+ 'Dark Desert Hint': (0x2e0, 0xd00),
+ 'Dark Desert Fairy': (0x1c0, 0xc90),
+ 'Spike Cave': (0x860, 0x180),
+ 'Cave Shop (Dark Death Mountain)': (0xd80, 0x180),
+ 'Dark Death Mountain Fairy': (0x620, 0x2c0),
+ 'Mimic Cave': (0xc80, 0x180),
+ 'Big Bomb Shop': (0x8b1, 0xb2d),
+ 'Dark Lake Hylia Shop': (0xa40, 0xc40),
+ 'Lumberjack House': (0x4e0, 0x0d0),
+ 'Lake Hylia Fortune Teller': (0xa40, 0xc40),
+ 'Kakariko Gamble Game': (0x2f0, 0xaf0)}
diff --git a/Fill.py b/Fill.py
index 46128ffd..c49e0ad0 100644
--- a/Fill.py
+++ b/Fill.py
@@ -2,174 +2,76 @@ import RaceRandom as random
import collections
import itertools
import logging
+import math
+from contextlib import suppress
-from BaseClasses import CollectionState
+from BaseClasses import CollectionState, FillError, LocationType
from Items import ItemFactory
from Regions import shop_to_location_table, retro_shops
+from source.item.FillUtil import filter_locations, classify_major_items, replace_trash_item, vanilla_fallback
+from source.item.FillUtil import filter_pot_locations, valid_pot_items
-class FillError(RuntimeError):
- pass
-
-def distribute_items_cutoff(world, cutoffrate=0.33):
- # get list of locations to fill in
- fill_locations = world.get_unfilled_locations()
- random.shuffle(fill_locations)
-
- # get items to distribute
- random.shuffle(world.itempool)
- itempool = world.itempool
-
- total_advancement_items = len([item for item in itempool if item.advancement])
- placed_advancement_items = 0
-
- progress_done = False
- advancement_placed = False
-
- # sweep once to pick up preplaced items
- world.state.sweep_for_events()
-
- while itempool and fill_locations:
- candidate_item_to_place = None
- item_to_place = None
- for item in itempool:
- if advancement_placed or (progress_done and (item.advancement or item.priority)):
- item_to_place = item
- break
- if item.advancement:
- candidate_item_to_place = item
- if world.unlocks_new_location(item):
- item_to_place = item
- placed_advancement_items += 1
- break
-
- if item_to_place is None:
- # check if we can reach all locations and that is why we find no new locations to place
- if not progress_done and len(world.get_reachable_locations()) == len(world.get_locations()):
- progress_done = True
- continue
- # check if we have now placed all advancement items
- if progress_done:
- advancement_placed = True
- continue
- # we might be in a situation where all new locations require multiple items to reach. If that is the case, just place any advancement item we've found and continue trying
- if candidate_item_to_place is not None:
- item_to_place = candidate_item_to_place
- placed_advancement_items += 1
- else:
- # we placed all available progress items. Maybe the game can be beaten anyway?
- if world.can_beat_game():
- logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.')
- progress_done = True
- continue
- raise FillError('No more progress items left to place.')
-
- spot_to_fill = None
- for location in fill_locations if placed_advancement_items / total_advancement_items < cutoffrate else reversed(fill_locations):
- if location.can_fill(world.state, item_to_place):
- spot_to_fill = location
- break
-
- if spot_to_fill is None:
- # we filled all reachable spots. Maybe the game can be beaten anyway?
- if world.can_beat_game():
- logging.getLogger('').warning('Not all items placed. Game beatable anyway.')
- break
- raise FillError('No more spots to place %s' % item_to_place)
-
- world.push_item(spot_to_fill, item_to_place, True)
- itempool.remove(item_to_place)
- fill_locations.remove(spot_to_fill)
- unplaced = [item.name for item in itempool]
- unfilled = [location.name for location in fill_locations]
- if unplaced or unfilled:
- logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
+def get_dungeon_item_pool(world):
+ return [item for dungeon in world.dungeons for item in dungeon.all_items]
-def distribute_items_staleness(world):
- # get list of locations to fill in
- fill_locations = world.get_unfilled_locations()
- random.shuffle(fill_locations)
+def promote_dungeon_items(world):
+ world.itempool += get_dungeon_item_pool(world)
- # get items to distribute
- random.shuffle(world.itempool)
- itempool = world.itempool
+ for item in world.get_items():
+ if item.smallkey or item.bigkey:
+ item.advancement = True
+ elif item.map or item.compass:
+ item.priority = True
+ dungeon_tracking(world)
- progress_done = False
- advancement_placed = False
- # sweep once to pick up preplaced items
- world.state.sweep_for_events()
+def dungeon_tracking(world):
+ for dungeon in world.dungeons:
+ layout = world.dungeon_layouts[dungeon.player][dungeon.name]
+ layout.dungeon_items = len([i for i in dungeon.all_items if i.is_inside_dungeon_item(world)])
+ layout.free_items = layout.location_cnt - layout.dungeon_items
- while itempool and fill_locations:
- candidate_item_to_place = None
- item_to_place = None
- for item in itempool:
- if advancement_placed or (progress_done and (item.advancement or item.priority)):
- item_to_place = item
- break
- if item.advancement:
- candidate_item_to_place = item
- if world.unlocks_new_location(item):
- item_to_place = item
- break
- if item_to_place is None:
- # check if we can reach all locations and that is why we find no new locations to place
- if not progress_done and len(world.get_reachable_locations()) == len(world.get_locations()):
- progress_done = True
- continue
- # check if we have now placed all advancement items
- if progress_done:
- advancement_placed = True
- continue
- # we might be in a situation where all new locations require multiple items to reach. If that is the case, just place any advancement item we've found and continue trying
- if candidate_item_to_place is not None:
- item_to_place = candidate_item_to_place
- else:
- # we placed all available progress items. Maybe the game can be beaten anyway?
- if world.can_beat_game():
- logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.')
- progress_done = True
- continue
- raise FillError('No more progress items left to place.')
+def fill_dungeons_restrictive(world, shuffled_locations):
+ dungeon_tracking(world)
- spot_to_fill = None
- for location in fill_locations:
- # increase likelyhood of skipping a location if it has been found stale
- if not progress_done and random.randint(0, location.staleness_count) > 2:
- continue
+ # with shuffled dungeon items they are distributed as part of the normal item pool
+ for item in world.get_items():
+ if (item.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]):
+ item.advancement = True
+ elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]):
+ item.priority = True
- if location.can_fill(world.state, item_to_place):
- spot_to_fill = location
- break
- else:
- location.staleness_count += 1
+ dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world)]
+ bigs, smalls, others = [], [], []
+ for i in dungeon_items:
+ (bigs if i.bigkey else smalls if i.smallkey else others).append(i)
+ unplaced_smalls = list(smalls)
+ for i in world.itempool:
+ if i.smallkey and world.keyshuffle[i.player]:
+ unplaced_smalls.append(i)
- # might have skipped too many locations due to potential staleness. Do not check for staleness now to find a candidate
- if spot_to_fill is None:
- for location in fill_locations:
- if location.can_fill(world.state, item_to_place):
- spot_to_fill = location
- break
+ def fill(base_state, items, key_pool):
+ fill_restrictive(world, base_state, shuffled_locations, items, key_pool, True)
- if spot_to_fill is None:
- # we filled all reachable spots. Maybe the game can be beaten anyway?
- if world.can_beat_game():
- logging.getLogger('').warning('Not all items placed. Game beatable anyway.')
- break
- raise FillError('No more spots to place %s' % item_to_place)
+ all_state_base = world.get_all_state()
+ big_state_base = all_state_base.copy()
+ for x in smalls + others:
+ big_state_base.collect(x, True)
+ fill(big_state_base, bigs, unplaced_smalls)
+ random.shuffle(shuffled_locations)
+ small_state_base = all_state_base.copy()
+ for x in others:
+ small_state_base.collect(x, True)
+ fill(small_state_base, smalls, unplaced_smalls)
+ random.shuffle(shuffled_locations)
+ fill(all_state_base, others, None)
- world.push_item(spot_to_fill, item_to_place, True)
- itempool.remove(item_to_place)
- fill_locations.remove(spot_to_fill)
- unplaced = [item.name for item in itempool]
- unfilled = [location.name for location in fill_locations]
- if unplaced or unfilled:
- logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
-
-def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool = None, single_player_placement = False):
+def fill_restrictive(world, base_state, locations, itempool, key_pool=None, single_player_placement=False,
+ vanilla=False):
def sweep_from_pool():
new_state = base_state.copy()
for item in itempool:
@@ -201,59 +103,96 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool =
spot_to_fill = None
- for location in locations:
- if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there
- location.item = item_to_place
- test_state = maximum_exploration_state.copy()
- test_state.stale[item_to_place.player] = True
- else:
- test_state = maximum_exploration_state
- if (not single_player_placement or location.player == item_to_place.player)\
- and location.can_fill(test_state, item_to_place, perform_access_check)\
- and valid_key_placement(item_to_place, location, itempool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool, world):
- spot_to_fill = location
+ item_locations = filter_locations(item_to_place, locations, world, vanilla)
+ for location in item_locations:
+ spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state,
+ single_player_placement, perform_access_check, key_pool, world)
+ if spot_to_fill:
break
- if item_to_place.smallkey or item_to_place.bigkey:
- location.item = None
-
if spot_to_fill is None:
- # we filled all reachable spots. Maybe the game can be beaten anyway?
- unplaced_items.insert(0, item_to_place)
- if world.can_beat_game():
- if world.accessibility[item_to_place.player] != 'none':
- logging.getLogger('').warning('Not all items placed. Game beatable anyway. (Could not place %s)' % item_to_place)
+ if vanilla:
+ unplaced_items.insert(0, item_to_place)
continue
- spot_to_fill = last_ditch_placement(item_to_place, locations, world, maximum_exploration_state,
- base_state, itempool, keys_in_itempool, single_player_placement)
+ spot_to_fill = recovery_placement(item_to_place, locations, world, maximum_exploration_state,
+ base_state, itempool, perform_access_check, item_locations,
+ key_pool, single_player_placement)
if spot_to_fill is None:
+ # we filled all reachable spots. Maybe the game can be beaten anyway?
+ unplaced_items.insert(0, item_to_place)
+ if world.can_beat_game():
+ if world.accessibility[item_to_place.player] != 'none':
+ logging.getLogger('').warning('Not all items placed. Game beatable anyway.'
+ f' (Could not place {item_to_place})')
+ continue
raise FillError('No more spots to place %s' % item_to_place)
world.push_item(spot_to_fill, item_to_place, False)
+ if item_to_place.smallkey:
+ with suppress(ValueError):
+ key_pool.remove(item_to_place)
track_outside_keys(item_to_place, spot_to_fill, world)
+ track_dungeon_items(item_to_place, spot_to_fill, world)
locations.remove(spot_to_fill)
spot_to_fill.event = True
itempool.extend(unplaced_items)
-def valid_key_placement(item, location, itempool, world):
- if (not item.smallkey and not item.bigkey) or item.player != location.player or world.retro[item.player] or world.logic[item.player] == 'nologic':
+def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_placement, perform_access_check,
+ key_pool, world):
+ if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there
+ location.item = item_to_place
+ test_state = max_exp_state.copy()
+ test_state.stale[item_to_place.player] = True
+ else:
+ test_state = max_exp_state
+ if not single_player_placement or location.player == item_to_place.player:
+ if location.can_fill(test_state, item_to_place, perform_access_check):
+ if valid_key_placement(item_to_place, location, key_pool, world):
+ if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world):
+ return location
+ if item_to_place.smallkey or item_to_place.bigkey:
+ location.item = None
+ return None
+
+
+def valid_key_placement(item, location, key_pool, world):
+ if not valid_reserved_placement(item, location, world):
+ return False
+ if ((not item.smallkey and not item.bigkey) or item.player != location.player
+ or world.retro[item.player] or world.logic[item.player] == 'nologic'):
return True
dungeon = location.parent_region.dungeon
if dungeon:
if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name):
return True
key_logic = world.key_logic[item.player][dungeon.name]
- unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player])
+ unplaced_keys = len([x for x in key_pool if x.name == key_logic.small_key_name and x.player == item.player])
prize_loc = None
if key_logic.prize_location:
prize_loc = world.get_location(key_logic.prize_location, location.player)
cr_count = world.crystals_needed_for_gt[location.player]
- return key_logic.check_placement(unplaced_keys, location if item.bigkey else None, prize_loc, cr_count)
+ wild_keys = world.keyshuffle[item.player]
+ return key_logic.check_placement(unplaced_keys, wild_keys, location if item.bigkey else None, prize_loc, cr_count)
else:
- inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player])
- or (item.bigkey and not world.bigkeyshuffle[item.player]))
- return not inside_dungeon_item
+ return not item.is_inside_dungeon_item(world)
+
+
+def valid_reserved_placement(item, location, world):
+ if item.player == location.player and item.is_inside_dungeon_item(world):
+ return location.name not in world.item_pool_config.reserved_locations[location.player]
+ return True
+
+
+def valid_dungeon_placement(item, location, world):
+ if location.parent_region.dungeon:
+ layout = world.dungeon_layouts[location.player][location.parent_region.dungeon.name]
+ if not is_dungeon_item(item, world) or item.player != location.player:
+ return layout.free_items > 0
+ else:
+ # the second half probably doesn't matter much - should always return true
+ return item.dungeon == location.parent_region.dungeon.name and layout.dungeon_items > 0
+ return not is_dungeon_item(item, world)
def track_outside_keys(item, location, world):
@@ -267,8 +206,74 @@ def track_outside_keys(item, location, world):
world.key_logic[item.player][item_dungeon].outside_keys += 1
+def track_dungeon_items(item, location, world):
+ if location.parent_region.dungeon and not item.crystal:
+ layout = world.dungeon_layouts[location.player][location.parent_region.dungeon.name]
+ if is_dungeon_item(item, world) and item.player == location.player:
+ layout.dungeon_items -= 1
+ else:
+ layout.free_items -= 1
+
+
+def is_dungeon_item(item, world):
+ return ((item.smallkey and not world.keyshuffle[item.player])
+ or (item.bigkey and not world.bigkeyshuffle[item.player])
+ or (item.compass and not world.compassshuffle[item.player])
+ or (item.map and not world.mapshuffle[item.player]))
+
+
+def recovery_placement(item_to_place, locations, world, state, base_state, itempool, perform_access_check, attempted,
+ key_pool=None, single_player_placement=False):
+ logging.getLogger('').debug(f'Could not place {item_to_place} attempting recovery')
+ if world.algorithm in ['balanced', 'equitable']:
+ return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, key_pool,
+ single_player_placement)
+ elif world.algorithm == 'vanilla_fill':
+ if item_to_place.type == 'Crystal':
+ possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal']
+ return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool,
+ key_pool, single_player_placement)
+ else:
+ i, config = 0, world.item_pool_config
+ tried = set(attempted)
+ if not item_to_place.is_inside_dungeon_item(world):
+ while i < len(config.location_groups[item_to_place.player]):
+ fallback_locations = config.location_groups[item_to_place.player][i].locations
+ other_locs = [x for x in locations if x.name in fallback_locations]
+ for location in other_locs:
+ spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
+ perform_access_check, key_pool, world)
+ if spot_to_fill:
+ return spot_to_fill
+ i += 1
+ tried.update(other_locs)
+ else:
+ other_locations = vanilla_fallback(item_to_place, locations, world)
+ for location in other_locations:
+ spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
+ perform_access_check, key_pool, world)
+ if spot_to_fill:
+ return spot_to_fill
+ tried.update(other_locations)
+ other_locations = [x for x in locations if x not in tried]
+ for location in other_locations:
+ spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
+ perform_access_check, key_pool, world)
+ if spot_to_fill:
+ return spot_to_fill
+ return None
+ else:
+ other_locations = [x for x in locations if x not in attempted]
+ for location in other_locations:
+ spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
+ perform_access_check, key_pool, world)
+ if spot_to_fill:
+ return spot_to_fill
+ return None
+
+
def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool,
- keys_in_itempool=None, single_player_placement=False):
+ key_pool=None, single_player_placement=False):
def location_preference(loc):
if not loc.item.advancement:
return 1
@@ -285,17 +290,22 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite
possible_swaps = [x for x in state.locations_checked
if x.item.type not in ['Event', 'Crystal'] and not x.forced_item]
swap_locations = sorted(possible_swaps, key=location_preference)
+ return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
+ key_pool, single_player_placement)
+
+def try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
+ key_pool=None, single_player_placement=False):
for location in swap_locations:
old_item = location.item
new_pool = list(itempool) + [old_item]
new_spot = find_spot_for_item(item_to_place, [location], world, base_state, new_pool,
- keys_in_itempool, single_player_placement)
+ key_pool, single_player_placement)
if new_spot:
restore_item = new_spot.item
new_spot.item = item_to_place
swap_spot = find_spot_for_item(old_item, locations, world, base_state, itempool,
- keys_in_itempool, single_player_placement)
+ key_pool, single_player_placement)
if swap_spot:
logging.getLogger('').debug(f'Swapping {old_item} for {item_to_place}')
world.push_item(swap_spot, old_item, False)
@@ -348,19 +358,60 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
random.shuffle(fill_locations)
# get items to distribute
+ classify_major_items(world)
+ # handle pot shuffle
+ pots_used = False
+ pot_item_pool = collections.defaultdict(list)
+
+ # guarantee one big magic in a bonk location
+ for player in range(1, world.players + 1):
+ if world.shuffle_bonk_drops[player]:
+ for item in world.itempool:
+ if item.name in ['Big Magic'] and item.player == player:
+ pot_item_pool[player].append(item)
+ break
+ from Regions import bonk_prize_table
+ for player, magic_pool in pot_item_pool.items():
+ if len(magic_pool) > 0:
+ world.itempool.remove(magic_pool[0])
+ pot_locations = [location for location in fill_locations if location.player == player
+ and location.name in [n for n, (_, _, aga, _, _, _) in bonk_prize_table.items() if not aga]]
+ pot_locations = filter_pot_locations(pot_locations, world)
+ fast_fill_helper(world, magic_pool, pot_locations)
+ pots_used = True
+
+ if pots_used:
+ fill_locations = world.get_unfilled_locations()
+ random.shuffle(fill_locations)
+
random.shuffle(world.itempool)
progitempool = [item for item in world.itempool if item.advancement]
prioitempool = [item for item in world.itempool if not item.advancement and item.priority]
restitempool = [item for item in world.itempool if not item.advancement and not item.priority]
+ gftower_trash &= world.algorithm in ['balanced', 'equitable', 'dungeon_only']
+ # dungeon only may fill up the dungeon... and push items out into the overworld
+
# fill in gtower locations with trash first
for player in range(1, world.players + 1):
- if not gftower_trash or not world.ganonstower_vanilla[player] or world.doorShuffle[player] == 'crossed' or world.logic[player] in ['owglitches', 'nologic']:
+ if (not gftower_trash or not world.ganonstower_vanilla[player]
+ or world.logic[player] in ['owglitches', 'nologic']):
continue
+ gt_count, total_count = calc_trash_locations(world, player)
+ scale_factor = .75 * (world.crystals_needed_for_gt[player] / 7)
+ if world.algorithm == 'dungeon_only':
+ reserved_space = sum(1 for i in progitempool+prioitempool if i.player == player)
+ max_trash = max(0, min(gt_count, total_count - reserved_space))
+ else:
+ max_trash = gt_count
+ scaled_trash = math.floor(max_trash * scale_factor)
+ if world.goal[player] in ['triforcehunt', 'trinity']:
+ gftower_trash_count = random.randint(scaled_trash, max_trash)
+ else:
+ gftower_trash_count = random.randint(0, scaled_trash)
- gftower_trash_count = (random.randint(15, 50) if world.goal[player] in ['triforcehunt', 'trinity'] else random.randint(0, 15))
-
- gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player]
+ gtower_locations = [location for location in fill_locations if location.parent_region.dungeon
+ and location.parent_region.dungeon.name == 'Ganons Tower' and location.player == player]
random.shuffle(gtower_locations)
trashcnt = 0
while gtower_locations and restitempool and trashcnt < gftower_trash_count:
@@ -376,20 +427,105 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
# todo: crossed
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0)
+ key_pool = [x for x in progitempool if x.smallkey]
- fill_restrictive(world, world.state, fill_locations, progitempool,
- keys_in_itempool={player: world.keyshuffle[player] for player in range(1, world.players + 1)})
-
+ # sort maps and compasses to the back -- this may not be viable in equitable & ambrosia
+ progitempool.sort(key=lambda item: 0 if item.map or item.compass else 1)
+ if world.algorithm == 'vanilla_fill':
+ fill_restrictive(world, world.state, fill_locations, progitempool, key_pool, vanilla=True)
+ fill_restrictive(world, world.state, fill_locations, progitempool, key_pool)
random.shuffle(fill_locations)
+ if world.algorithm == 'balanced':
+ fast_fill(world, prioitempool, fill_locations)
+ elif world.algorithm == 'vanilla_fill':
+ fast_vanilla_fill(world, prioitempool, fill_locations)
+ elif world.algorithm in ['major_only', 'dungeon_only', 'district']:
+ filtered_fill(world, prioitempool, fill_locations)
+ else: # just need to ensure dungeon items still get placed in dungeons
+ fast_equitable_fill(world, prioitempool, fill_locations)
+ # placeholder work
+ if world.algorithm == 'district':
+ random.shuffle(fill_locations)
+ placeholder_items = [item for item in world.itempool if item.name == 'Rupee (1)']
+ num_ph_items = len(placeholder_items)
+ if num_ph_items > 0:
+ placeholder_locations = filter_locations('Placeholder', fill_locations, world)
+ num_ph_locations = len(placeholder_locations)
+ if num_ph_items < num_ph_locations < len(fill_locations):
+ for _ in range(num_ph_locations - num_ph_items):
+ placeholder_items.append(replace_trash_item(restitempool, 'Rupee (1)'))
+ assert len(placeholder_items) == len(placeholder_locations)
+ for i in placeholder_items:
+ restitempool.remove(i)
+ for l in placeholder_locations:
+ fill_locations.remove(l)
+ filtered_fill(world, placeholder_items, placeholder_locations)
- fast_fill(world, prioitempool, fill_locations)
-
- fast_fill(world, restitempool, fill_locations)
+ if world.players > 1:
+ fast_fill_pot_for_multiworld(world, restitempool, fill_locations)
+ if world.algorithm == 'vanilla_fill':
+ fast_vanilla_fill(world, restitempool, fill_locations)
+ else:
+ fast_fill(world, restitempool, fill_locations)
unplaced = [item.name for item in prioitempool + restitempool]
unfilled = [location.name for location in fill_locations]
if unplaced or unfilled:
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
+ ensure_good_pots(world)
+
+
+def calc_trash_locations(world, player):
+ total_count, gt_count = 0, 0
+ for loc in world.get_locations():
+ if (loc.player == player and loc.item is None
+ and (loc.type not in {LocationType.Bonk, LocationType.Pot, LocationType.Drop, LocationType.Normal} or not loc.forced_item)
+ and (loc.type != LocationType.Shop or world.shopsanity[player])
+ and loc.parent_region.dungeon):
+ total_count += 1
+ if loc.parent_region.dungeon.name == 'Ganons Tower':
+ gt_count += 1
+ return gt_count, total_count
+
+
+def ensure_good_pots(world, write_skips=False):
+ for loc in world.get_locations():
+ # # convert Arrows 5 when necessary
+ # if (loc.item.name in {'Arrows (5)'}
+ # and loc.type not in [LocationType.Pot, LocationType.Bonk]):
+ # loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.item.player)
+ # convert Nothing when necessary
+ if (loc.item.name in {'Nothing'}
+ and (loc.type != LocationType.Pot or loc.item.player != loc.player)):
+ loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.item.player)
+ # # can be placed here by multiworld balancing or shop balancing
+ # # change it to something normal for the player it got swapped to
+ # elif (loc.item.name in {'Chicken', 'Big Magic'}
+ # and (loc.type != LocationType.Pot or loc.item.player != loc.player)):
+ # if loc.type == LocationType.Pot:
+ # loc.item.player = loc.player
+ # else:
+ # loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.player)
+ # do the arrow retro check
+ if world.retro[loc.item.player] and loc.item.name in {'Arrows (5)', 'Arrows (10)'}:
+ loc.item = ItemFactory('Rupees (5)', loc.item.player)
+ # don't write out all pots to spoiler
+ if write_skips:
+ if loc.type == LocationType.Pot and loc.item.name in valid_pot_items:
+ loc.skip = True
+
+
+invalid_location_replacement = {'Arrows (5)': 'Arrows (10)', 'Nothing': 'Rupees (5)',
+ 'Chicken': 'Rupees (5)', 'Big Magic': 'Small Magic'}
+
+
+def fast_fill_helper(world, item_pool, fill_locations):
+ if world.algorithm == 'vanilla_fill':
+ fast_vanilla_fill(world, item_pool, fill_locations)
+ else:
+ fast_fill(world, item_pool, fill_locations)
+ # todo: other fast fill methods?
+
def fast_fill(world, item_pool, fill_locations):
while item_pool and fill_locations:
@@ -398,77 +534,81 @@ def fast_fill(world, item_pool, fill_locations):
world.push_item(spot_to_fill, item_to_place, False)
-def flood_items(world):
- # get items to distribute
- random.shuffle(world.itempool)
- itempool = world.itempool
- progress_done = False
+def fast_fill_pot_for_multiworld(world, item_pool, fill_locations):
+ pot_item_pool = collections.defaultdict(list)
+ pot_fill_locations = collections.defaultdict(list)
+ for item in item_pool:
+ if item.name in valid_pot_items:
+ pot_item_pool[item.player].append(item)
+ for loc in fill_locations:
+ if loc.type == LocationType.Pot:
+ pot_fill_locations[loc.player].append(loc)
+ for player in range(1, world.players+1):
+ flex = 256 - world.pot_contents[player].multiworld_count
+ fill_count = len(pot_fill_locations[player]) - flex
+ if fill_count > 0:
+ fill_spots = random.sample(pot_fill_locations[player], fill_count)
+ fill_items = random.sample(pot_item_pool[player], fill_count)
+ for x in fill_items:
+ item_pool.remove(x)
+ for x in fill_spots:
+ fill_locations.remove(x)
+ fast_fill(world, fill_items, fill_spots)
+
+
+def filtered_fill(world, item_pool, fill_locations):
+ while item_pool and fill_locations:
+ item_to_place = item_pool.pop()
+ item_locations = filter_locations(item_to_place, fill_locations, world)
+ spot_to_fill = next(iter(item_locations))
+ fill_locations.remove(spot_to_fill)
+ world.push_item(spot_to_fill, item_to_place, False)
# sweep once to pick up preplaced items
world.state.sweep_for_events()
- # fill world from top of itempool while we can
- while not progress_done:
- location_list = world.get_unfilled_locations()
- random.shuffle(location_list)
- spot_to_fill = None
- for location in location_list:
- if location.can_fill(world.state, itempool[0]):
- spot_to_fill = location
- break
- if spot_to_fill:
- item = itempool.pop(0)
- world.push_item(spot_to_fill, item, True)
- continue
+def fast_vanilla_fill(world, item_pool, fill_locations):
+ next_item_pool = []
+ while item_pool and fill_locations:
+ item_to_place = item_pool.pop()
+ locations = filter_locations(item_to_place, fill_locations, world, vanilla_skip=True)
+ if len(locations):
+ spot_to_fill = locations.pop()
+ fill_locations.remove(spot_to_fill)
+ world.push_item(spot_to_fill, item_to_place, False)
+ else:
+ next_item_pool.append(item_to_place)
+ while next_item_pool and fill_locations:
+ item_to_place = next_item_pool.pop()
+ spot_to_fill = next(iter(filter_locations(item_to_place, fill_locations, world)))
+ fill_locations.remove(spot_to_fill)
+ world.push_item(spot_to_fill, item_to_place, False)
- # ran out of spots, check if we need to step in and correct things
- if len(world.get_reachable_locations()) == len(world.get_locations()):
- progress_done = True
- continue
- # need to place a progress item instead of an already placed item, find candidate
- item_to_place = None
- candidate_item_to_place = None
- for item in itempool:
- if item.advancement:
- candidate_item_to_place = item
- if world.unlocks_new_location(item):
- item_to_place = item
- break
+def filtered_equitable_fill(world, item_pool, fill_locations):
+ while item_pool and fill_locations:
+ item_to_place = item_pool.pop()
+ item_locations = filter_locations(item_to_place, fill_locations, world)
+ spot_to_fill = next(l for l in item_locations if valid_dungeon_placement(item_to_place, l, world))
+ fill_locations.remove(spot_to_fill)
+ world.push_item(spot_to_fill, item_to_place, False)
+ track_dungeon_items(item_to_place, spot_to_fill, world)
- # we might be in a situation where all new locations require multiple items to reach. If that is the case, just place any advancement item we've found and continue trying
- if item_to_place is None:
- if candidate_item_to_place is not None:
- item_to_place = candidate_item_to_place
- else:
- raise FillError('No more progress items left to place.')
- # find item to replace with progress item
- location_list = world.get_reachable_locations()
- random.shuffle(location_list)
- for location in location_list:
- if location.item is not None and not location.item.advancement and not location.item.priority and not location.item.smallkey and not location.item.bigkey:
- # safe to replace
- replace_item = location.item
- replace_item.location = None
- itempool.append(replace_item)
- world.push_item(location, item_to_place, True)
- itempool.remove(item_to_place)
- break
+def fast_equitable_fill(world, item_pool, fill_locations):
+ while item_pool and fill_locations:
+ item_to_place = item_pool.pop()
+ spot_to_fill = next(l for l in fill_locations if valid_dungeon_placement(item_to_place, l, world))
+ fill_locations.remove(spot_to_fill)
+ world.push_item(spot_to_fill, item_to_place, False)
+ track_dungeon_items(item_to_place, spot_to_fill, world)
def lock_shop_locations(world, player):
for shop, loc_names in shop_to_location_table.items():
for loc in loc_names:
- world.get_location(loc, player).event = True
world.get_location(loc, player).locked = True
- # I don't believe these locations exist in non-shopsanity
- # if world.retro[player]:
- # for shop, loc_names in retro_shops.items():
- # for loc in loc_names:
- # world.get_location(loc, player).event = True
- # world.get_location(loc, player).locked = True
def sell_potions(world, player):
@@ -479,7 +619,7 @@ def sell_potions(world, player):
loc_choices += [world.get_location(loc, player) for loc in shop_to_location_table[shop.region.name]]
locations = [loc for loc in loc_choices if not loc.item]
for potion in ['Green Potion', 'Blue Potion', 'Red Potion', 'Bee']:
- location = random.choice(locations)
+ location = random.choice(filter_locations(ItemFactory(potion, player), locations, world, potion=True))
locations.remove(location)
p_item = next(item for item in world.itempool if item.name == potion and item.player == player)
world.push_item(location, p_item, collect=False)
@@ -490,7 +630,9 @@ def sell_keys(world, player):
# exclude the old man or take any caves because free keys are too good
shop_names = {shop.region.name: shop for shop in world.shops[player] if shop.region.name in shop_to_location_table}
choices = [(world.get_location(loc, player), shop) for shop in shop_names for loc in shop_to_location_table[shop]]
- locations = [(loc, shop) for loc, shop in choices if not loc.item]
+ locations = [l for l, shop in choices]
+ locations = filter_locations(ItemFactory('Small Key (Universal)', player), locations, world)
+ locations = [(loc, shop) for loc, shop in choices if not loc.item and loc in locations]
location, shop = random.choice(locations)
universal_key = next(i for i in world.itempool if i.name == 'Small Key (Universal)' and i.player == player)
world.push_item(location, universal_key, collect=False)
@@ -615,12 +757,14 @@ def balance_multiworld_progression(world):
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
-def check_shop_swap(l):
+def check_shop_swap(l, make_item_free=False):
if l.parent_region.name in shop_to_location_table:
if l.name in shop_to_location_table[l.parent_region.name]:
idx = shop_to_location_table[l.parent_region.name].index(l.name)
inv_slot = l.parent_region.shop.inventory[idx]
inv_slot['item'] = l.item.name
+ if make_item_free:
+ inv_slot['price'] = 0
elif l.parent_region in retro_shops:
idx = retro_shops[l.parent_region.name].index(l.name)
inv_slot = l.parent_region.shop.inventory[idx]
@@ -695,7 +839,12 @@ def balance_money_progression(world):
return False
done = False
+ attempts = world.players * 20 + 20
while not done:
+ attempts -= 1
+ if attempts < 0:
+ from DungeonGenerator import GenerationException
+ raise GenerationException(f'Infinite loop detected at "balance_money_progression"')
sphere_costs = {player: 0 for player in range(1, world.players+1)}
locked_by_money = {player: set() for player in range(1, world.players+1)}
sphere_locations = get_sphere_locations(state, unchecked_locations)
@@ -781,12 +930,13 @@ def balance_money_progression(world):
if len(increase_targets) == 0:
raise Exception('No early sphere swaps for rupees - money grind would be required - bailing for now')
best_target = min(increase_targets, key=lambda t: rupee_chart[t.item.name] if t.item.name in rupee_chart else 0)
- old_value = rupee_chart[best_target.item.name] if best_target.item.name in rupee_chart else 0
+ make_item_free = wallet[target_player] < 20
+ old_value = 0 if make_item_free else (rupee_chart[best_target.item.name] if best_target.item.name in rupee_chart else 0)
if best_swap is None:
logger.debug(f'Upgrading {best_target.item.name} @ {best_target.name} for 300 Rupees')
best_target.item = ItemFactory('Rupees (300)', best_target.item.player)
best_target.item.location = best_target
- check_shop_swap(best_target.item.location)
+ check_shop_swap(best_target.item.location, make_item_free)
else:
old_item = best_target.item
logger.debug(f'Swapping {best_target.item.name} @ {best_target.name} for {best_swap.item.name} @ {best_swap.name}')
@@ -794,7 +944,7 @@ def balance_money_progression(world):
best_target.item.location = best_target
best_swap.item = old_item
best_swap.item.location = best_swap
- check_shop_swap(best_target.item.location)
+ check_shop_swap(best_target.item.location, make_item_free)
check_shop_swap(best_swap.item.location)
increase = best_value - old_value
difference -= increase
diff --git a/Gui.py b/Gui.py
index f6fc7d62..948b2b4e 100755
--- a/Gui.py
+++ b/Gui.py
@@ -1,3 +1,7 @@
+if __name__ == '__main__':
+ from source.meta.check_requirements import check_requirements
+ check_requirements()
+
import json
import os
import sys
@@ -106,7 +110,7 @@ def guiMain(args=None):
self.pages["startinventory"] = ttk.Frame(self.notebook)
self.pages["custom"] = ttk.Frame(self.notebook)
self.notebook.add(self.pages["randomizer"], text='Randomize')
- self.notebook.add(self.pages["adjust"], text='Adjust')
+ self.notebook.add(self.pages["adjust"], text='Adjust/Patch')
self.notebook.add(self.pages["startinventory"], text='Starting Inventory')
self.notebook.add(self.pages["custom"], text='Custom Item Pool')
self.notebook.pack()
diff --git a/InitialSram.py b/InitialSram.py
new file mode 100644
index 00000000..4af02457
--- /dev/null
+++ b/InitialSram.py
@@ -0,0 +1,274 @@
+from dataclasses import dataclass, field
+from typing import List
+
+from BaseClasses import CollectionState
+from Utils import count_set_bits
+
+SRAM_SIZE = 0x500
+ROOM_DATA = 0x000
+OVERWORLD_DATA = 0x280
+
+def _new_default_sram():
+ sram_buf = [0x00] * 0x500
+ sram_buf[ROOM_DATA+0x20D] = 0xF0
+ sram_buf[ROOM_DATA+0x20F] = 0xF0
+ sram_buf[0x379] = 0x68
+ sram_buf[0x401] = 0xFF
+ sram_buf[0x402] = 0xFF
+ return sram_buf
+
+@dataclass
+class InitialSram:
+ _initial_sram_bytes: List[int] = field(default_factory=_new_default_sram)
+
+ def _set_value(self, idx: int, val:int):
+ if idx > SRAM_SIZE:
+ raise IndexError('SRAM index out of bounds: {idx}')
+ if not (-1 < val < 256):
+ raise ValueError('SRAM value must be between 0 and 255: {val}')
+ self._initial_sram_bytes[idx] = val
+
+ def _or_value(self, idx: int, val:int):
+ if idx > SRAM_SIZE:
+ raise IndexError('SRAM index out of bounds: {idx}')
+ if not (-1 < val < 256):
+ raise ValueError('SRAM value must be between 0 and 255: {val}')
+ self._initial_sram_bytes[idx] |= val
+
+ def pre_open_aga_curtains(self):
+ self._or_value(ROOM_DATA+0x61, 0x80)
+
+ def pre_open_skullwoods_curtains(self):
+ self._or_value(ROOM_DATA+0x93, 0x80)
+
+ def pre_open_lumberjack(self):
+ self._or_value(OVERWORLD_DATA+0x02, 0x20)
+
+ def pre_open_castle_gate(self):
+ self._or_value(OVERWORLD_DATA+0x1B, 0x20)
+
+ def pre_open_ganons_tower(self):
+ self._or_value(OVERWORLD_DATA+0x43, 0x20)
+
+ def pre_open_pyramid_hole(self):
+ self._or_value(OVERWORLD_DATA+0x5B, 0x20)
+
+ def pre_set_overworld_flag(self, owid, bitmask):
+ self._or_value(OVERWORLD_DATA+owid, bitmask)
+
+ def set_starting_equipment(self, world: object, player: int):
+ equip = [0] * (0x340 + 0x50)
+ equip[0x36C] = 0x18
+ equip[0x36D] = 0x18
+ equip[0x379] = 0x68
+ if world.bombbag[player]:
+ starting_max_bombs = 0
+ else:
+ starting_max_bombs = 10
+ starting_max_arrows = 30
+ starting_bomb_cap_upgrades = 0
+ starting_arrow_cap_upgrades = 0
+ starting_bombs = 0
+ starting_arrows = 0
+
+ startingstate = CollectionState(world)
+
+ if startingstate.has('Bow', player):
+ equip[0x340] = 3 if startingstate.has('Silver Arrows', player) else 1
+ equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases
+ if not world.retro[player]:
+ equip[0x38E] |= 0x80
+ if startingstate.has('Silver Arrows', player):
+ equip[0x38E] |= 0x40
+
+ if startingstate.has('Titans Mitts', player):
+ equip[0x354] = 2
+ elif startingstate.has('Power Glove', player):
+ equip[0x354] = 1
+
+ if startingstate.has('Golden Sword', player):
+ equip[0x359] = 4
+ elif startingstate.has('Tempered Sword', player):
+ equip[0x359] = 3
+ elif startingstate.has('Master Sword', player):
+ equip[0x359] = 2
+ elif startingstate.has('Fighter Sword', player):
+ equip[0x359] = 1
+
+ if startingstate.has('L5 Bombs', player) or startingstate.has('L5 Cane', player):
+ equip[0x38F] = 5
+ elif startingstate.has('L4 Bombs', player) or startingstate.has('L4 Cane', player):
+ equip[0x38F] = 4
+ elif startingstate.has('L3 Bombs', player) or startingstate.has('L3 Cane', player):
+ equip[0x38F] = 3
+ elif startingstate.has('L2 Bombs', player) or startingstate.has('L2 Cane', player):
+ equip[0x38F] = 2
+ elif startingstate.has('L1 Bombs', player) or startingstate.has('L1 Cane', player) or world.swords[player] == 'cane':
+ equip[0x38F] = 1
+ if startingstate.has('L1 Cane', player) or startingstate.has('L2 Cane', player) or startingstate.has('L3 Cane', player) or startingstate.has('L4 Cane', player) or startingstate.has('L5 Cane', player):
+ if world.swords[player] == 'byrna':
+ equip[0x351] = 1
+ elif world.swords[player] == 'somaria':
+ equip[0x350] = 1
+
+ if startingstate.has('Mirror Shield', player):
+ equip[0x35A] = 3
+ elif startingstate.has('Red Shield', player):
+ equip[0x35A] = 2
+ elif startingstate.has('Blue Shield', player):
+ equip[0x35A] = 1
+
+ if startingstate.has('Red Mail', player):
+ equip[0x35B] = 2
+ elif startingstate.has('Blue Mail', player):
+ equip[0x35B] = 1
+
+ if startingstate.has('Magic Upgrade (1/4)', player):
+ equip[0x37B] = 2
+ equip[0x36E] = 0x80
+ elif startingstate.has('Magic Upgrade (1/2)', player):
+ equip[0x37B] = 1
+ equip[0x36E] = 0x80
+
+ for item in world.precollected_items:
+ if item.player != player:
+ continue
+
+ if item.name in ['Bow', 'Silver Arrows', 'Progressive Bow', 'Progressive Bow (Alt)',
+ 'Titans Mitts', 'Power Glove', 'Progressive Glove',
+ 'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword',
+ 'L5 Bombs', 'L4 Bombs', 'L3 Bombs', 'L2 Bombs', 'L1 Bombs', 'Progressive Bombs',
+ 'L5 Cane', 'L4 Cane', 'L3 Cane', 'L2 Cane', 'L1 Cane', 'Progressive Cane',
+ 'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield',
+ 'Red Mail', 'Blue Mail', 'Progressive Armor',
+ 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']:
+ continue
+
+ set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2),
+ 'Cape': (0x352, 1), 'Lamp': (0x34A, 1), 'Moon Pearl': (0x357, 1), 'Cane of Somaria': (0x350, 1), 'Cane of Byrna': (0x351, 1),
+ 'Fire Rod': (0x345, 1), 'Ice Rod': (0x346, 1), 'Bombos': (0x347, 1), 'Ether': (0x348, 1), 'Quake': (0x349, 1)}
+ or_table = {'Green Pendant': (0x374, 0x04), 'Red Pendant': (0x374, 0x01), 'Blue Pendant': (0x374, 0x02),
+ 'Crystal 1': (0x37A, 0x02), 'Crystal 2': (0x37A, 0x10), 'Crystal 3': (0x37A, 0x40), 'Crystal 4': (0x37A, 0x20),
+ 'Crystal 5': (0x37A, 0x04), 'Crystal 6': (0x37A, 0x01), 'Crystal 7': (0x37A, 0x08),
+ 'Big Key (Eastern Palace)': (0x367, 0x20), 'Compass (Eastern Palace)': (0x365, 0x20), 'Map (Eastern Palace)': (0x369, 0x20),
+ 'Big Key (Desert Palace)': (0x367, 0x10), 'Compass (Desert Palace)': (0x365, 0x10), 'Map (Desert Palace)': (0x369, 0x10),
+ 'Big Key (Tower of Hera)': (0x366, 0x20), 'Compass (Tower of Hera)': (0x364, 0x20), 'Map (Tower of Hera)': (0x368, 0x20),
+ 'Big Key (Escape)': (0x367, 0xC0), 'Compass (Escape)': (0x365, 0xC0), 'Map (Escape)': (0x369, 0xC0),
+ 'Big Key (Agahnims Tower)': (0x367, 0x08), 'Compass (Agahnims Tower)': (0x365, 0x08), 'Map (Agahnims Tower)': (0x369, 0x08),
+ 'Big Key (Palace of Darkness)': (0x367, 0x02), 'Compass (Palace of Darkness)': (0x365, 0x02), 'Map (Palace of Darkness)': (0x369, 0x02),
+ 'Big Key (Thieves Town)': (0x366, 0x10), 'Compass (Thieves Town)': (0x364, 0x10), 'Map (Thieves Town)': (0x368, 0x10),
+ 'Big Key (Skull Woods)': (0x366, 0x80), 'Compass (Skull Woods)': (0x364, 0x80), 'Map (Skull Woods)': (0x368, 0x80),
+ 'Big Key (Swamp Palace)': (0x367, 0x04), 'Compass (Swamp Palace)': (0x365, 0x04), 'Map (Swamp Palace)': (0x369, 0x04),
+ 'Big Key (Ice Palace)': (0x366, 0x40), 'Compass (Ice Palace)': (0x364, 0x40), 'Map (Ice Palace)': (0x368, 0x40),
+ 'Big Key (Misery Mire)': (0x367, 0x01), 'Compass (Misery Mire)': (0x365, 0x01), 'Map (Misery Mire)': (0x369, 0x01),
+ 'Big Key (Turtle Rock)': (0x366, 0x08), 'Compass (Turtle Rock)': (0x364, 0x08), 'Map (Turtle Rock)': (0x368, 0x08),
+ 'Big Key (Ganons Tower)': (0x366, 0x04), 'Compass (Ganons Tower)': (0x364, 0x04), 'Map (Ganons Tower)': (0x368, 0x04)}
+ set_or_table = {'Flippers': (0x356, 1, 0x379, 0x02),'Pegasus Boots': (0x355, 1, 0x379, 0x04),
+ 'Shovel': (0x34C, 1, 0x38C, 0x04), 'Ocarina': (0x34C, 3, 0x38C, 0x01),
+ 'Mushroom': (0x344, 1, 0x38C, 0x20 | 0x08), 'Magic Powder': (0x344, 2, 0x38C, 0x10),
+ 'Blue Boomerang': (0x341, 1, 0x38C, 0x80), 'Red Boomerang': (0x341, 2, 0x38C, 0x40)}
+ keys = {'Small Key (Eastern Palace)': [0x37E], 'Small Key (Desert Palace)': [0x37F],
+ 'Small Key (Tower of Hera)': [0x386],
+ 'Small Key (Agahnims Tower)': [0x380], 'Small Key (Palace of Darkness)': [0x382],
+ 'Small Key (Thieves Town)': [0x387],
+ 'Small Key (Skull Woods)': [0x384], 'Small Key (Swamp Palace)': [0x381],
+ 'Small Key (Ice Palace)': [0x385],
+ 'Small Key (Misery Mire)': [0x383], 'Small Key (Turtle Rock)': [0x388],
+ 'Small Key (Ganons Tower)': [0x389],
+ 'Small Key (Universal)': [0x38B], 'Small Key (Escape)': [0x37C, 0x37D]}
+ bottles = {'Bottle': 2, 'Bottle (Red Potion)': 3, 'Bottle (Green Potion)': 4, 'Bottle (Blue Potion)': 5,
+ 'Bottle (Fairy)': 6, 'Bottle (Bee)': 7, 'Bottle (Good Bee)': 8}
+ rupees = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)': 50, 'Rupees (100)': 100, 'Rupees (300)': 300}
+ bomb_caps = {'Bomb Upgrade (+5)': 5, 'Bomb Upgrade (+10)': 10}
+ arrow_caps = {'Arrow Upgrade (+5)': 5, 'Arrow Upgrade (+10)': 10}
+ bombs = {'Single Bomb': 1, 'Bombs (3)': 3, 'Bombs (10)': 10}
+ arrows = {'Single Arrow': 1, 'Arrows (10)': 10}
+
+ if item.name in set_table:
+ equip[set_table[item.name][0]] = set_table[item.name][1]
+ elif item.name in or_table:
+ equip[or_table[item.name][0]] |= or_table[item.name][1]
+ elif item.name in set_or_table:
+ equip[set_or_table[item.name][0]] = set_or_table[item.name][1]
+ equip[set_or_table[item.name][2]] |= set_or_table[item.name][3]
+ elif item.name in keys:
+ for address in keys[item.name]:
+ equip[address] = min(equip[address] + 1, 99)
+ elif item.name in bottles:
+ if equip[0x34F] < world.difficulty_requirements[player].progressive_bottle_limit:
+ equip[0x35C + equip[0x34F]] = bottles[item.name]
+ equip[0x34F] += 1
+ elif item.name in rupees:
+ equip[0x360:0x362] = list(min(equip[0x360] + (equip[0x361] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False))
+ equip[0x362:0x364] = list(min(equip[0x362] + (equip[0x363] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False))
+ elif item.name in bomb_caps:
+ starting_bomb_cap_upgrades += bomb_caps[item.name]
+ elif item.name in arrow_caps:
+ starting_arrow_cap_upgrades += arrow_caps[item.name]
+ elif item.name in bombs:
+ starting_bombs += bombs[item.name]
+ elif item.name in arrows:
+ if world.retro[player]:
+ equip[0x38E] |= 0x80
+ starting_arrows = 1
+ else:
+ starting_arrows += arrows[item.name]
+ elif item.name in ['Piece of Heart', 'Boss Heart Container', 'Sanctuary Heart Container']:
+ if item.name == 'Piece of Heart':
+ equip[0x36B] = (equip[0x36B] + 1) % 4
+ if item.name != 'Piece of Heart' or equip[0x36B] == 0:
+ equip[0x36C] = min(equip[0x36C] + 0x08, 0xA0)
+ equip[0x36D] = min(equip[0x36D] + 0x08, 0xA0)
+ else:
+ raise RuntimeError(f'Unsupported item in starting equipment: {item.name}')
+
+ equip[0x370] = min(starting_bomb_cap_upgrades, 50)
+ equip[0x371] = min(starting_arrow_cap_upgrades, 70)
+ equip[0x343] = min(starting_bombs, (equip[0x370] + starting_max_bombs))
+ equip[0x377] = min(starting_arrows, (equip[0x371] + starting_max_arrows))
+
+ # Assertion and copy equip to initial_sram_bytes
+ assert equip[:0x340] == [0] * 0x340
+ self._initial_sram_bytes[0x340:0x390] = equip[0x340:0x390]
+
+ # Set counters and highest equipment values
+ self._initial_sram_bytes[0x471] = count_set_bits(self._initial_sram_bytes[0x37A])
+ self._initial_sram_bytes[0x429] = count_set_bits(self._initial_sram_bytes[0x374])
+ self._initial_sram_bytes[0x417] = self._initial_sram_bytes[0x359]
+ self._initial_sram_bytes[0x422] = self._initial_sram_bytes[0x35A]
+ self._initial_sram_bytes[0x46E] = self._initial_sram_bytes[0x35B]
+
+ if world.swords[player] == "swordless":
+ self._initial_sram_bytes[0x359] = 0xFF
+ self._initial_sram_bytes[0x417] = 0x00
+
+ def set_starting_rupees(self, rupees: int):
+ if not (-1 < rupees < 10000):
+ raise ValueError("Starting rupees must be between 0 and 9999")
+ self._initial_sram_bytes[0x362] = self._initial_sram_bytes[0x360] = rupees & 0xFF
+ self._initial_sram_bytes[0x363] = self._initial_sram_bytes[0x361] = rupees >> 8
+
+ def set_progress_indicator(self, indicator: int):
+ self._set_value(0x3C5, indicator)
+
+ def set_progress_flags(self, flags: int):
+ self._set_value(0x3C6, flags)
+
+ def set_starting_entrance(self, entrance: int):
+ self._set_value(0x3C8, entrance)
+
+ def set_starting_timer(self, seconds: int):
+ timer = (seconds * 60).to_bytes(4, "little")
+ self._initial_sram_bytes[0x454] = timer[0]
+ self._initial_sram_bytes[0x455] = timer[1]
+ self._initial_sram_bytes[0x456] = timer[2]
+ self._initial_sram_bytes[0x457] = timer[3]
+
+ def set_swordless_curtains(self):
+ self._or_value(ROOM_DATA+0x61, 0x80)
+ self._or_value(ROOM_DATA+0x93, 0x80)
+
+ def get_initial_sram(self):
+ assert len(self._initial_sram_bytes) == SRAM_SIZE
+
+ return self._initial_sram_bytes[:]
diff --git a/ItemList.py b/ItemList.py
index bbddf1d6..b7b20672 100644
--- a/ItemList.py
+++ b/ItemList.py
@@ -3,13 +3,16 @@ import logging
import math
import RaceRandom as random
-from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState
-from Dungeons import get_dungeon_item_pool
+from BaseClasses import LocationType, Region, RegionType, Shop, ShopType, Location, CollectionState, PotItem
from EntranceShuffle import connect_entrance
-from Regions import shop_to_location_table, retro_shops, shop_table_by_location
-from Fill import FillError, fill_restrictive, fast_fill
+from Regions import shop_to_location_table, retro_shops, shop_table_by_location, valid_pot_location
+from Fill import FillError, fill_restrictive, fast_fill, get_dungeon_item_pool
+from PotShuffle import vanilla_pots
+from Tables import bonk_prize_lookup
from Items import ItemFactory
+from source.item.FillUtil import trash_items, pot_items
+
import source.classes.constants as CONST
@@ -42,6 +45,7 @@ Difficulty = namedtuple('Difficulty',
'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit'])
total_items_to_place = 153
+max_goal = 850
difficulties = {
'normal': Difficulty(
@@ -208,6 +212,7 @@ def generate_itempool(world, player):
world.push_item(loc, ItemFactory('Triforce', player), False)
loc.event = True
loc.locked = True
+ loc.forced_item = loc.item
world.get_location('Ganon', player).event = True
world.get_location('Ganon', player).locked = True
@@ -259,6 +264,9 @@ def generate_itempool(world, player):
world.push_item(world.get_location('Ice Block Drop', player), ItemFactory('Convenient Block', player), False)
world.get_location('Ice Block Drop', player).event = True
world.get_location('Ice Block Drop', player).locked = True
+ world.push_item(world.get_location('Skull Star Tile', player), ItemFactory('Hidden Pits', player), False)
+ world.get_location('Skull Star Tile', player).event = True
+ world.get_location('Skull Star Tile', player).locked = True
if world.mode[player] == 'standard':
world.push_item(world.get_location('Zelda Pickup', player), ItemFactory('Zelda Herself', player), False)
world.get_location('Zelda Pickup', player).event = True
@@ -277,8 +285,12 @@ def generate_itempool(world, player):
if player in world.pool_adjustment.keys():
amt = world.pool_adjustment[player]
if amt < 0:
- for _ in range(amt, 0):
- pool.remove(next(iter([x for x in pool if x in ['Rupees (20)', 'Rupees (5)', 'Rupee (1)']])))
+ trash_options = [x for x in pool if x in trash_items]
+ random.shuffle(trash_options)
+ trash_options = sorted(trash_options, key=lambda x: trash_items[x], reverse=True)
+ while amt > 0 and len(trash_options) > 0:
+ pool.remove(trash_options.pop())
+ amt -= 1
elif amt > 0:
for _ in range(0, amt):
pool.append('Rupees (20)')
@@ -369,8 +381,10 @@ def generate_itempool(world, player):
if clock_mode is not None:
world.clock_mode = clock_mode
- if world.goal[player] in ['triforcehunt', 'trinity']:
- world.treasure_hunt_count[player], world.treasure_hunt_total[player] = set_default_triforce(world.goal[player], world.treasure_hunt_count[player], world.treasure_hunt_total[player])
+ goal = world.goal[player]
+ if goal in ['triforcehunt', 'trinity']:
+ g, t = set_default_triforce(goal, world.treasure_hunt_count[player], world.treasure_hunt_total[player])
+ world.treasure_hunt_count[player], world.treasure_hunt_total[player] = g, t
world.treasure_hunt_icon[player] = 'Triforce Piece'
world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player
@@ -420,11 +434,20 @@ def generate_itempool(world, player):
if world.retro[player]:
set_up_take_anys(world, player)
- if world.keydropshuffle[player]:
- world.itempool += [ItemFactory('Small Key (Universal)', player)] * 32
+ if world.dropshuffle[player]:
+ world.itempool += [ItemFactory('Small Key (Universal)', player)] * 13
+ if world.pottery[player] not in ['none', 'cave']:
+ world.itempool += [ItemFactory('Small Key (Universal)', player)] * 19
create_dynamic_shop_locations(world, player)
+ if world.pottery[player] not in ['none', 'keys']:
+ add_pot_contents(world, player)
+
+ if world.shuffle_bonk_drops[player]:
+ create_dynamic_bonkdrop_locations(world, player)
+ add_bonkdrop_contents(world, player)
+
take_any_locations = [
'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut',
@@ -445,7 +468,9 @@ def set_up_take_anys(world, player):
if 'Archery Game' in take_any_locations:
take_any_locations.remove('Archery Game')
- regions = random.sample(take_any_locations, 5)
+ take_any_candidates = [x for x in take_any_locations if len(world.get_region(x, player).locations) == 0]
+
+ regions = random.sample(take_any_candidates, 5)
old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave, 'the sword cave', player)
world.regions.append(old_man_take_any)
@@ -512,6 +537,21 @@ def create_dynamic_shop_locations(world, player):
loc.locked = True
+def create_dynamic_bonkdrop_locations(world, player):
+ from Regions import bonk_prize_table
+ for bonk_location, (_, _, _, _, region_name, hint_text) in bonk_prize_table.items():
+ region = world.get_region(region_name, player)
+ loc = Location(player, bonk_location, 0, region, hint_text)
+ loc.type = LocationType.Bonk
+ loc.parent_region = region
+ loc.address = 0x2abb00 + (bonk_prize_table[loc.name][0] * 6) + 3
+
+ region.locations.append(loc)
+ world.dynamic_locations.append(loc)
+
+ world.clear_location_cache()
+
+
def fill_prizes(world, attempts=15):
all_state = world.get_all_state(keys=True)
for player in range(1, world.players + 1):
@@ -537,7 +577,7 @@ def fill_prizes(world, attempts=15):
continue
break
else:
- raise FillError('Unable to place dungeon prizes')
+ raise FillError(f'Unable to place dungeon prizes {", ".join(list(map(lambda d: d.hint_text, prize_locs)))}')
def set_up_shops(world, player):
@@ -594,7 +634,7 @@ def set_up_shops(world, player):
removals = [item for item in world.itempool if item.name == 'Bomb Upgrade (+5)' and item.player == player]
for remove in removals:
world.itempool.remove(remove)
- world.itempool.append(ItemFactory('Rupees (50)', player)) # replace the bomb upgrade
+ world.itempool.append(ItemFactory('Rupees (50)', player)) # replace the bomb upgrade
else:
cap_shop = world.get_region('Capacity Upgrade', player).shop
cap_shop.inventory[0] = cap_shop.inventory[1] # remove bomb capacity upgrades in bombbag
@@ -797,15 +837,36 @@ rupee_chart = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)'
'Rupees (100)': 100, 'Rupees (300)': 300}
+def add_pot_contents(world, player):
+ for super_tile, pot_list in vanilla_pots.items():
+ for pot in pot_list:
+ if pot.item not in [PotItem.Hole, PotItem.Key, PotItem.Switch]:
+ if valid_pot_location(pot, world.pot_pool[player], world, player):
+ item = ('Rupees (5)' if world.retro[player] and pot_items[pot.item] == 'Arrows (5)'
+ else pot_items[pot.item])
+ world.itempool.append(ItemFactory(item, player))
+
+
+def add_bonkdrop_contents(world, player):
+ from Items import item_table
+ for item_name, (_, count, alt_item) in bonk_prize_lookup.items():
+ if item_name not in item_table:
+ item_name = alt_item
+ while (count > 0):
+ item = ItemFactory(item_name, player)
+ world.itempool.append(item)
+ count -= 1
+
+
def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic, flute_activated):
pool = []
placed_items = {}
precollected_items = []
clock_mode = None
- if goal in ['triforcehunt', 'trinity']:
- if treasure_hunt_total == 0:
- treasure_hunt_total = 30
- triforcepool = ['Triforce Piece'] * int(treasure_hunt_total)
+ if treasure_hunt_total == 0 and goal in ['triforcehunt', 'trinity']:
+ treasure_hunt_total = 30 if goal == 'triforcehunt' else 10
+ # triforce pieces max out
+ triforcepool = ['Triforce Piece'] * min(treasure_hunt_total, max_goal)
pool.extend(alwaysitems)
@@ -834,7 +895,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer,
lamps_needed_for_dark_rooms = 1
# insanity shuffle doesn't have fake LW/DW logic so for now guaranteed Mirror and Moon Pearl at the start
- if shuffle == 'insanity_legacy':
+ if shuffle == 'insanity_legacy':
place_item('Link\'s House', 'Magic Mirror')
place_item('Sanctuary', 'Moon Pearl')
else:
@@ -902,6 +963,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer,
place_item('Master Sword Pedestal', swords_to_use.pop())
else:
place_item('Master Sword Pedestal', 'Triforce')
+ pool.append(swords_to_use.pop())
else:
pool.extend(diff.progressivesword if want_progressives() else diff.basicsword)
if swords in ['assured', 'assured_pseudo']:
@@ -913,26 +975,19 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer,
pool.remove('Fighter Sword')
pool.extend(['Rupees (50)'])
- extraitems = total_items_to_place - len(pool) - len(placed_items)
-
if timer in ['timed', 'timed-countdown']:
pool.extend(diff.timedother)
- extraitems -= len(diff.timedother)
clock_mode = 'stopwatch' if timer == 'timed' else 'countdown'
elif timer == 'timed-ohko':
pool.extend(diff.timedohko)
- extraitems -= len(diff.timedohko)
clock_mode = 'countdown-ohko'
if goal in ['triforcehunt', 'trinity']:
pool.extend(triforcepool)
- extraitems -= len(triforcepool)
for extra in diff.extras:
- if extraitems > 0:
- if len(extra) > extraitems:
- extra = random.choices(extra, k=extraitems)
- pool.extend(extra)
- extraitems -= len(extra)
+ pool.extend(extra)
+
+ # note: massage item pool now handles shrinking the pool appropriately
if goal in ['pedestal', 'trinity'] and swords != 'vanilla':
place_item('Master Sword Pedestal', 'Triforce')
@@ -963,6 +1018,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer,
pool = [item.replace('Bomb Upgrade (+10)', 'Small Heart') for item in pool]
return (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms)
+
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bombbag, customitemarray):
pool = []
placed_items = {}
@@ -989,7 +1045,8 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
# Triforce Pieces
if goal in ['triforcehunt', 'trinity']:
- customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"] = set_default_triforce(goal, customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"])
+ g, t = set_default_triforce(goal, customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"])
+ customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"] = g, t
itemtotal = 0
# Bow to Silver Arrows Upgrade, including Generic Keys & Rupoors
@@ -1021,7 +1078,15 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
pool.append(thisbottle)
if customitemarray["triforcepieces"] > 0 or customitemarray["triforcepiecesgoal"] > 0:
+ # Location pool doesn't support larger values
+ treasure_hunt_count = max(min(customitemarray["triforcepiecesgoal"], max_goal), 1)
treasure_hunt_icon = 'Triforce Piece'
+ # Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling.
+ if ((customitemarray["triforcepieces"] < treasure_hunt_count) and (goal in ['triforcehunt', 'trinity'])
+ and (customitemarray["triforce"] == 0)):
+ extrapieces = treasure_hunt_count - customitemarray["triforcepieces"]
+ pool.extend(['Triforce Piece'] * extrapieces)
+ itemtotal = itemtotal + extrapieces
if timer in ['display', 'timed', 'timed-countdown']:
clock_mode = 'countdown' if timer == 'timed-countdown' else 'stopwatch'
@@ -1077,6 +1142,54 @@ def set_default_triforce(goal, custom_goal, custom_total):
triforce_total = max(min(custom_total, 128), triforce_goal) #128 max to ensure other progression can fit.
return (triforce_goal, triforce_total)
+
+def make_customizer_pool(world, player):
+ pool = []
+ placed_items = {}
+ precollected_items = []
+ clock_mode = None
+
+ def place_item(loc, item):
+ assert loc not in placed_items
+ placed_items[loc] = item
+
+ diff = difficulties[world.difficulty[player]]
+ for item_name, amount in world.customizer.get_item_pool()[player].items():
+ if isinstance(amount, int):
+ if item_name == 'Bottle (Random)':
+ for _ in range(amount):
+ pool.append(random.choice(diff.bottles))
+ else:
+ pool.extend([item_name] * amount)
+
+ timer = world.timer[player]
+ if timer in ['display', 'timed', 'timed-countdown']:
+ clock_mode = 'countdown' if timer == 'timed-countdown' else 'stopwatch'
+ elif timer == 'timed-ohko':
+ clock_mode = 'countdown-ohko'
+ elif timer == 'ohko':
+ clock_mode = 'ohko'
+
+ if world.goal[player] == 'pedestal':
+ place_item('Master Sword Pedestal', 'Triforce')
+
+ return pool, placed_items, precollected_items, clock_mode, 1
+
+
+# location pool doesn't support larger values at this time
+def set_default_triforce(goal, custom_goal, custom_total):
+ triforce_goal, triforce_total = 0, 0
+ if goal == 'triforcehunt':
+ triforce_goal, triforce_total = 20, 30
+ elif goal == 'trinity':
+ triforce_goal, triforce_total = 8, 10
+ if custom_goal > 0:
+ triforce_goal = max(min(custom_goal, max_goal), 1)
+ if custom_total > 0:
+ triforce_total = max(min(custom_total, max_goal), triforce_goal)
+ return triforce_goal, triforce_total
+
+
# A quick test to ensure all combinations generate the correct amount of items.
def test():
for difficulty in ['normal', 'hard', 'expert']:
@@ -1106,27 +1219,3 @@ def test():
if __name__ == '__main__':
test()
-
-
-def fill_specific_items(world):
- keypool = [item for item in world.itempool if item.smallkey]
- cage = world.get_location('Tower of Hera - Basement Cage', 1)
- c_dungeon = cage.parent_region.dungeon
- key_item = next(x for x in keypool if c_dungeon.name in x.name or (c_dungeon.name == 'Hyrule Castle' and 'Escape' in x.name))
- world.itempool.remove(key_item)
- all_state = world.get_all_state(True)
- fill_restrictive(world, all_state, [cage], [key_item])
-
- location = world.get_location('Tower of Hera - Map Chest', 1)
- key_item = next(x for x in world.itempool if 'Byrna' in x.name)
- world.itempool.remove(key_item)
- fast_fill(world, [key_item], [location])
-
-
- # somaria = next(item for item in world.itempool if item.name == 'Cane of Somaria')
- # shooter = world.get_location('Palace of Darkness - Shooter Room', 1)
- # world.itempool.remove(somaria)
- # all_state = world.get_all_state(True)
- # fill_restrictive(world, all_state, [shooter], [somaria])
-
-
diff --git a/Items.py b/Items.py
index f54ebcb3..ff6df66a 100644
--- a/Items.py
+++ b/Items.py
@@ -22,12 +22,12 @@ def ItemFactory(items, player):
return ret
-# Format: Name: (Advancement, Priority, Type, ItemCode, Cost, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text)
+# Format: Name: (Advancement, Priority, Type, ItemCode, BasePrice, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text)
item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the Bow'),
'Progressive Bow': (True, False, None, 0x64, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
'Progressive Bow (Alt)': (True, False, None, 0x65, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
'Book of Mudora': (True, False, None, 0x1D, 150, 'This is a\nparadox?!', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again', 'the Book'),
- 'Hammer': (True, False, None, 0x09, 250, 'stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'the Hammer'),
+ 'Hammer': (True, False, None, 0x09, 250, 'Stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'the Hammer'),
'Hookshot': (True, False, None, 0x0A, 250, 'BOING!!!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'the Hookshot'),
'Magic Mirror': (True, False, None, 0x1A, 250, 'Isn\'t your\nreflection so\npretty?', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again', 'the Mirror'),
'Ocarina': (True, False, None, 0x14, 250, 'Save the duck\nand fly to\nfreedom!', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the Flute'),
@@ -38,65 +38,70 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche
'Mushroom': (True, False, None, 0x29, 50, 'I\'m a fun guy!\n\nI\'m a funghi!', 'and the legal drugs', 'the drug-dealing kid', 'legal drugs for sale', 'shroom swap', 'shroom boy sells drugs again', 'the Mushroom'),
'Shovel': (True, False, None, 0x13, 50, 'Can\n You\n Dig it?', 'and the spade', 'archaeologist kid', 'dirt spade for sale', 'can you dig it', 'shovel boy digs again', 'the Shovel'),
'Lamp': (True, False, None, 0x12, 150, 'Baby, baby,\nbaby.\nLight my way!', 'and the flashlight', 'light-shining kid', 'flashlight for sale', 'fungus for illumination', 'illuminated boy can see again', 'the Lamp'),
- 'Magic Powder': (True, False, None, 0x0D, 50, 'you can turn\nanti-faeries\ninto faeries', 'and the magic sack', 'the sack-holding kid', 'magic sack for sale', 'the witch and assistant', 'magic boy plays marbles again', 'the Powder'),
+ 'Magic Powder': (True, False, None, 0x0D, 50, 'You can turn\nanti-faeries\ninto faeries', 'and the magic sack', 'the sack-holding kid', 'magic sack for sale', 'the witch and assistant', 'magic boy plays marbles again', 'the Powder'),
'Moon Pearl': (True, False, None, 0x1F, 200, ' Bunny Link\n be\n gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'the Moon Pearl'),
'Cane of Somaria': (True, False, None, 0x15, 250, 'I make blocks\nto hold down\nswitches!', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'the Red Cane'),
'Fire Rod': (True, False, None, 0x07, 250, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the Fire Rod'),
- 'Flippers': (True, False, None, 0x1E, 250, 'fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the flippers'),
+ 'Flippers': (True, False, None, 0x1E, 250, 'Fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the Flippers'),
'Ice Rod': (True, False, None, 0x08, 250, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the Ice Rod'),
'Titans Mitts': (True, False, None, 0x1C, 200, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the Mitts'),
'Bombos': (True, False, None, 0x0F, 100, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'),
'Ether': (True, False, None, 0x10, 100, 'This magic\ncoin freezes\neverything!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor again', 'Ether'),
'Quake': (True, False, None, 0x11, 100, 'Maxing out the\nRichter scale\nis what I do!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'medallion boy shakes dirt again', 'Quake'),
- 'Bottle': (True, False, None, 0x16, 50, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'a Bottle'),
- 'Bottle (Red Potion)': (True, False, None, 0x2B, 70, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a Bottle'),
- 'Bottle (Green Potion)': (True, False, None, 0x2C, 60, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a Bottle'),
- 'Bottle (Blue Potion)': (True, False, None, 0x2D, 80, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a Bottle'),
- 'Bottle (Fairy)': (True, False, None, 0x3D, 70, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'a Bottle'),
- 'Bottle (Bee)': (True, False, None, 0x3C, 50, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a Bottle'),
- 'Bottle (Good Bee)': (True, False, None, 0x48, 60, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a Bottle'),
+ 'Bottle': (True, False, None, 0x16, 50, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'a bottle'),
+ 'Bottle (Red Potion)': (True, False, None, 0x2B, 70, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a bottle'),
+ 'Bottle (Green Potion)': (True, False, None, 0x2C, 60, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a bottle'),
+ 'Bottle (Blue Potion)': (True, False, None, 0x2D, 80, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a bottle'),
+ 'Bottle (Fairy)': (True, False, None, 0x3D, 70, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'a bottle'),
+ 'Bottle (Bee)': (True, False, None, 0x3C, 50, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bottle'),
+ 'Bottle (Good Bee)': (True, False, None, 0x48, 60, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a bottle'),
'Master Sword': (True, False, 'Sword', 0x50, 100, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'),
'Tempered Sword': (True, False, 'Sword', 0x02, 150, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'),
'Fighter Sword': (True, False, 'Sword', 0x49, 50, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the Fighter Sword'),
+ 'Sword and Shield': (True, False, 'Sword', 0x00, 'An uncle\nsword rests\nhere!', 'the sword and shield', 'sword and shield-wielding kid', 'training set for sale', 'fungus for training set', 'sword and shield boy fights again', 'the small sword and shield'),
'Golden Sword': (True, False, 'Sword', 0x03, 200, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'),
- 'Progressive Sword': (True, False, 'Sword', 0x5E, 150, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a Sword'),
- 'Progressive Glove': (True, False, None, 0x61, 150, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a Glove'),
+ 'Progressive Sword': (True, False, 'Sword', 0x5E, 150, 'A better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a Sword'),
+ 'Progressive Glove': (True, False, None, 0x61, 150, 'A way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a Glove'),
'Silver Arrows': (True, False, None, 0x58, 100, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'the Silver Arrows'),
- 'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], 999, None, None, None, None, None, None, None),
- 'Blue Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x02], 999, None, None, None, None, None, None, None),
- 'Red Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x03], 999, None, None, None, None, None, None, None),
+ 'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01, 0x08], 999, None, None, None, None, None, None, None),
+ 'Blue Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x02, 0x09], 999, None, None, None, None, None, None, None),
+ 'Red Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x03, 0x0a], 999, None, None, None, None, None, None, None),
'Triforce': (True, False, None, 0x6A, 777, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'the Triforce'),
- 'Power Star': (True, False, None, 0x6B, 100, 'a small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'),
- 'Triforce Piece': (True, False, None, 0x6C, 100, 'a small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce Piece'),
- 'Crystal 1': (True, False, 'Crystal', [0x02, 0x34, 0x64, 0x40, 0x7F, 0x06], 999, None, None, None, None, None, None, None),
- 'Crystal 2': (True, False, 'Crystal', [0x10, 0x34, 0x64, 0x40, 0x79, 0x06], 999, None, None, None, None, None, None, None),
- 'Crystal 3': (True, False, 'Crystal', [0x40, 0x34, 0x64, 0x40, 0x6C, 0x06], 999, None, None, None, None, None, None, None),
- 'Crystal 4': (True, False, 'Crystal', [0x20, 0x34, 0x64, 0x40, 0x6D, 0x06], 999, None, None, None, None, None, None, None),
- 'Crystal 5': (True, False, 'Crystal', [0x04, 0x32, 0x64, 0x40, 0x6E, 0x06], 999, None, None, None, None, None, None, None),
- 'Crystal 6': (True, False, 'Crystal', [0x01, 0x32, 0x64, 0x40, 0x6F, 0x06], 999, None, None, None, None, None, None, None),
- 'Crystal 7': (True, False, 'Crystal', [0x08, 0x34, 0x64, 0x40, 0x7C, 0x06], 999, None, None, None, None, None, None, None),
- 'Single Arrow': (False, False, None, 0x43, 3, 'a lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'),
+ 'Power Star': (True, False, None, 0x6B, 100, 'A small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'),
+ 'Triforce Piece': (True, False, None, 0x6C, 100, 'A small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce piece'),
+ 'Crystal 1': (True, False, 'Crystal', [0x02, 0x34, 0x64, 0x40, 0x7F, 0x06, 0x01], 999, None, None, None, None, None, None, None),
+ 'Crystal 2': (True, False, 'Crystal', [0x10, 0x34, 0x64, 0x40, 0x79, 0x06, 0x02], 999, None, None, None, None, None, None, None),
+ 'Crystal 3': (True, False, 'Crystal', [0x40, 0x34, 0x64, 0x40, 0x6C, 0x06, 0x03], 999, None, None, None, None, None, None, None),
+ 'Crystal 4': (True, False, 'Crystal', [0x20, 0x34, 0x64, 0x40, 0x6D, 0x06, 0x04], 999, None, None, None, None, None, None, None),
+ 'Crystal 5': (True, False, 'Crystal', [0x04, 0x32, 0x64, 0x40, 0x6E, 0x06, 0x05], 999, None, None, None, None, None, None, None),
+ 'Crystal 6': (True, False, 'Crystal', [0x01, 0x32, 0x64, 0x40, 0x6F, 0x06, 0x06], 999, None, None, None, None, None, None, None),
+ 'Crystal 7': (True, False, 'Crystal', [0x08, 0x34, 0x64, 0x40, 0x7C, 0x06, 0x07], 999, None, None, None, None, None, None, None),
+ 'Single Arrow': (False, False, None, 0x43, 3, 'A lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'),
'Arrows (10)': (False, False, None, 0x44, 30, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'ten arrows'),
- 'Arrow Upgrade (+10)': (False, False, None, 0x54, 100, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
- 'Arrow Upgrade (+5)': (False, False, None, 0x53, 100, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
+ 'Arrow Upgrade (+10)': (False, False, None, 0x54, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
+ 'Arrow Upgrade (+5)': (False, False, None, 0x53, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Single Bomb': (False, False, None, 0x27, 5, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'),
+ 'Arrows (5)': (False, False, None, 0xB5, 15, 'This will give\nyou five shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'five arrows'),
+ 'Small Magic': (False, False, None, 0x45, 5, 'A bit of magic', 'and the bit of magic', 'bit-o-magic kid', 'magic bit for sale', 'fungus for magic', 'magic boy conjures again', 'a bit of magic'),
+ 'Big Magic': (False, False, None, 0xB4, 40, 'A lot of magic', 'and lots of magic', 'lot-o-magic kid', 'magic refill for sale', 'fungus for magic', 'magic boy conjures again', 'a magic refill'),
+ 'Chicken': (False, False, None, 0xB3, 999, 'Cucco of Legend', 'and the legendary cucco', 'chicken kid', 'fried chicken for sale', 'fungus for chicken', 'cucco boy clucks again', 'a cucco'),
'Bombs (3)': (False, False, None, 0x28, 15, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'),
'Bombs (10)': (False, False, None, 0x31, 50, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'),
- 'Bomb Upgrade (+10)': (False, False, None, 0x52, 100, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
- 'Bomb Upgrade (+5)': (False, False, None, 0x51, 100, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
+ 'Bomb Upgrade (+10)': (False, False, None, 0x52, 100, 'Increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
+ 'Bomb Upgrade (+5)': (False, False, None, 0x51, 100, 'Increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
'Blue Mail': (False, True, None, 0x22, 50, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'the Blue Mail'),
'Red Mail': (False, True, None, 0x23, 100, 'Now you\'re a\nred elf!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'the Red Mail'),
- 'Progressive Armor': (False, True, None, 0x60, 50, 'time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'),
+ 'Progressive Armor': (False, True, None, 0x60, 50, 'Time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'),
'Blue Boomerang': (True, False, None, 0x0C, 50, 'No matter what\nyou do, blue\nreturns to you', 'and the bluemarang', 'the bat-throwing kid', 'bent stick for sale', 'fungus for puma-stick', 'throwing boy plays fetch again', 'the Blue Boomerang'),
'Red Boomerang': (True, False, None, 0x2A, 50, 'No matter what\nyou do, red\nreturns to you', 'and the badmarang', 'the bat-throwing kid', 'air foil for sale', 'fungus for return-stick', 'magical boy plays fetch again', 'the Red Boomerang'),
- 'Blue Shield': (False, True, None, 0x04, 50, 'Now you can\ndefend against\npebbles!', 'and the stone blocker', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'the Blue Shield'),
- 'Red Shield': (False, True, None, 0x05, 500, 'Now you can\ndefend against\nfireballs!', 'and the shot blocker', 'shield-wielding kid', 'fire shield for sale', 'fungus for fire shield', 'shield boy defends again', 'the Red Shield'),
+ 'Blue Shield': (False, True, None, 0x04, 50, 'Now you can\ndefend against\npebbles!', 'and the stone blocker', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a Blue Shield'),
+ 'Red Shield': (False, True, None, 0x05, 500, 'Now you can\ndefend against\nfireballs!', 'and the shot blocker', 'shield-wielding kid', 'fire shield for sale', 'fungus for fire shield', 'shield boy defends again', 'a Red Shield'),
'Mirror Shield': (True, False, None, 0x06, 200, 'Now you can\ndefend against\nlasers!', 'and the laser blocker', 'shield-wielding kid', 'face shield for sale', 'fungus for face shield', 'shield boy defends again', 'the Mirror Shield'),
- 'Progressive Shield': (True, False, None, 0x5F, 50, 'have a better\nblocker in\nfront of you', 'and the new shield', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a shield'),
+ 'Progressive Shield': (True, False, None, 0x5F, 50, 'Have a better\nblocker in\nfront of you', 'and the new shield', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a shield'),
'Bug Catching Net': (True, False, None, 0x21, 50, 'Let\'s catch\nsome bees and\nfaeries!', 'and the bee catcher', 'the bug-catching kid', 'stick web for sale', 'fungus for butterflies', 'wrong boy catches bees again', 'the Bug Net'),
'Cane of Byrna': (True, False, None, 0x18, 50, 'Use this to\nbecome\ninvincible!', 'and the bad cane', 'the spark-making kid', 'spark stick for sale', 'spark-stick for trade', 'cane boy encircles again', 'the Blue Cane'),
- 'Boss Heart Container': (False, False, None, 0x3E, 40, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'),
- 'Sanctuary Heart Container': (False, False, None, 0x3F, 50, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'),
+ 'Boss Heart Container': (False, True, None, 0x3E, 40, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'),
+ 'Sanctuary Heart Container': (False, True, None, 0x3F, 50, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'),
'Piece of Heart': (False, False, None, 0x17, 10, 'Just a little\npiece of love!', 'and the broken heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart piece'),
'Rupee (1)': (False, False, None, 0x34, 0, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a green rupee'),
'Rupees (5)': (False, False, None, 0x35, 2, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a blue rupee'),
@@ -104,14 +109,14 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche
'Rupees (50)': (False, False, None, 0x41, 25, 'A rupee pile!\nOkay?', 'and the rupee pile', 'the well-off kid', 'life lesson for sale', 'buying okay drugs', 'destitute boy has dinner again', 'fifty rupees'),
'Rupees (100)': (False, False, None, 0x40, 50, 'A rupee stash!\nHell yeah!', 'and the rupee stash', 'the kind-of-rich kid', 'life lesson for sale', 'buying good drugs', 'affluent boy goes drinking again', 'one hundred rupees'),
'Rupees (300)': (False, False, None, 0x46, 150, 'A rupee hoard!\nHell yeah!', 'and the rupee hoard', 'the really-rich kid', 'life lesson for sale', 'buying the best drugs', 'fat-cat boy is rich again', 'three hundred rupees'),
- 'Rupoor': (False, False, None, 0x59, 0, 'a debt collector', 'and the toll-booth', 'the toll-booth kid', 'double loss for sale', 'witch stole your rupees', 'affluent boy steals rupees', 'a rupoor'),
- 'Red Clock': (False, True, None, 0x5B, 0, 'a waste of time', 'the ruby clock', 'the ruby-time kid', 'red time for sale', 'for ruby time', 'moment boy travels time again', 'a red clock'),
- 'Blue Clock': (False, True, None, 0x5C, 50, 'a bit of time', 'the sapphire clock', 'sapphire-time kid', 'blue time for sale', 'for sapphire time', 'moment boy time travels again', 'a blue clock'),
- 'Green Clock': (False, True, None, 0x5D, 200, 'a lot of time', 'the emerald clock', 'the emerald-time kid', 'green time for sale', 'for emerald time', 'moment boy adjusts time again', 'a red clock'),
- 'Single RNG': (False, True, None, 0x62, 300, 'something you don\'t yet have', None, None, None, None, 'unknown boy somethings again', 'a new mystery'),
- 'Multi RNG': (False, True, None, 0x63, 100, 'something you may already have', None, None, None, None, 'unknown boy somethings again', 'a total mystery'),
- 'Magic Upgrade (1/2)': (True, False, None, 0x4E, 50, 'Your magic\npower has been\ndoubled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Half magic'), # can be required to beat mothula in an open seed in very very rare circumstance
- 'Magic Upgrade (1/4)': (True, False, None, 0x4F, 100, 'Your magic\npower has been\nquadrupled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Quarter magic'), # can be required to beat mothula in an open seed in very very rare circumstance
+ 'Rupoor': (False, False, None, 0x59, 0, 'A debt collector', 'and the toll-booth', 'the toll-booth kid', 'double loss for sale', 'witch stole your rupees', 'affluent boy steals rupees', 'a rupoor'),
+ 'Red Clock': (False, True, None, 0x5B, 0, 'A waste of time', 'the ruby clock', 'the ruby-time kid', 'red time for sale', 'for ruby time', 'moment boy travels time again', 'a red clock'),
+ 'Blue Clock': (False, True, None, 0x5C, 50, 'A bit of time', 'the sapphire clock', 'sapphire-time kid', 'blue time for sale', 'for sapphire time', 'moment boy time travels again', 'a blue clock'),
+ 'Green Clock': (False, True, None, 0x5D, 200, 'A lot of time', 'the emerald clock', 'the emerald-time kid', 'green time for sale', 'for emerald time', 'moment boy adjusts time again', 'a red clock'),
+ 'Single RNG': (False, True, None, 0x62, 300, 'Something you don\'t yet have', None, None, None, None, 'unknown boy somethings again', 'a new mystery'),
+ 'Multi RNG': (False, True, None, 0x63, 100, 'Something you may already have', None, None, None, None, 'unknown boy somethings again', 'a total mystery'),
+ 'Magic Upgrade (1/2)': (True, False, None, 0x4E, 50, 'Your magic\npower has been\ndoubled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Half Magic'), # can be required to beat mothula in an open seed in very very rare circumstance
+ 'Magic Upgrade (1/4)': (True, False, None, 0x4F, 100, 'Your magic\npower has been\nquadrupled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Quarter Magic'), # can be required to beat mothula in an open seed in very very rare circumstance
'Small Key (Eastern Palace)': (False, False, 'SmallKey', 0xA2, 40, 'A small key to Armos Knights', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Eastern Palace'),
'Big Key (Eastern Palace)': (False, False, 'BigKey', 0x9D, 60, 'A big key to Armos Knights', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a Big Key to Eastern Palace'),
'Compass (Eastern Palace)': (False, True, 'Compass', 0x8D, 10, 'Now you can find the Armos Knights!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a Compass to Eastern Palace'),
@@ -167,23 +172,15 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche
'Small Key (Universal)': (False, True, None, 0xAF, 100, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key'),
'Nothing': (False, False, None, 0x5A, 1, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again', 'nothing'),
'Bee Trap': (False, False, None, 0xB0, 50, 'We will sting your face a whole lot!', 'and the sting buddies', 'the beekeeper kid', 'insects for sale', 'shroom pollenation', 'bottle boy has mad bees again', 'friendship'),
- 'L1 Bombs': (True, False, 'SwordBomb', 0xB1, 50, 'Some basic\nexplosives\nrest here!', 'the basic grenades', 'the bomb-holding kid', 'booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'basic bombs'),
- 'L2 Bombs': (True, False, 'SwordBomb', 0xB2, 100, 'Some decent\nexplosives\nrest here!', 'the decent grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'),
- 'L3 Bombs': (True, False, 'SwordBomb', 0xB3, 150, 'Some good\nexplosives\nrest here!', 'the good grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'),
- 'L4 Bombs': (True, False, 'SwordBomb', 0xB4, 200, 'The golden\nexplosives\nrest here!', 'the golden grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'),
- 'L5 Bombs': (True, False, 'SwordBomb', 0xB5, 200, 'The golden\nexplosives\nrest here!', 'the golden grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'),
'Progressive Bombs': (True, False, 'SwordBomb', 0xB6, 200, 'throw more\npowerful\nexplosives', 'the unknown grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'),
- 'L1 Cane': (True, False, 'SwordCane', 0xB7, 50, 'A basic\nstick\nrests here!', 'the basic stick', 'the stick-holding kid', 'stick for sale', 'fungus into stick', 'cane boy improves again', 'basic cane'),
- 'L2 Cane': (True, False, 'SwordCane', 0xB8, 100, 'A decent\nstick\nrests here!', 'the decent stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'),
- 'L3 Cane': (True, False, 'SwordCane', 0xB9, 150, 'A good\nstick\nrests here!', 'the good stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'),
- 'L4 Cane': (True, False, 'SwordCane', 0xBA, 200, 'A golden\nstick\nrests here!', 'the golden stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'),
- 'L5 Cane': (True, False, 'SwordCane', 0xBB, 200, 'A golden\nstick\nrests here!', 'the golden stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'),
- 'Progressive Cane': (True, False, 'SwordCane', 0xBC, 200, 'a better\nstick\nrests here!', 'the unknown stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'),
+ 'Progressive Cane': (True, False, 'SwordCane', 0xB7, 200, 'a better\nstick\nrests here!', 'the unknown stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'),
'Red Potion': (False, False, None, 0x2E, 150, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a red potion'),
'Green Potion': (False, False, None, 0x2F, 60, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a green potion'),
'Blue Potion': (False, False, None, 0x30, 160, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a blue potion'),
'Bee': (False, False, None, 0x0E, 10, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bee'),
'Small Heart': (False, False, None, 0x42, 10, 'Just a little\npiece of love!', 'and the heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart'),
+ 'Apples': (False, False, None, 0xB1, 30, 'Just a few pieces of fruit!', 'and the juicy fruit', 'the fruity kid', 'the fruit stand', 'expired fruit', 'bottle boy has fruit again', 'an apple hoard'),
+ 'Fairy': (False, False, None, 0xB2, 50, 'Just a pixie!', 'and the pixie', 'the pixie kid', 'pixie for sale', 'pixie fungus', 'bottle boy has pixie again', 'a pixie'),
'Beat Agahnim 1': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Beat Agahnim 2': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Get Frog': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
@@ -200,6 +197,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche
'Maiden Rescued': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Maiden Unmasked': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Convenient Block': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
+ 'Hidden Pits': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Zelda Herself': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Zelda Delivered': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
}
diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py
index 83bcba43..fc250852 100644
--- a/KeyDoorShuffle.py
+++ b/KeyDoorShuffle.py
@@ -5,7 +5,8 @@ from collections import defaultdict, deque
from BaseClasses import DoorType, dungeon_keys, KeyRuleType, RegionType
from Regions import dungeon_events
from Dungeons import dungeon_keys, dungeon_bigs, dungeon_table
-from DungeonGenerator import ExplorationState, special_big_key_doors
+from DungeonGenerator import ExplorationState, special_big_key_doors, count_locations_exclude_big_chest, prize_or_event
+from DungeonGenerator import reserved_location, blind_boss_unavail
class KeyLayout(object):
@@ -61,9 +62,9 @@ class KeyLogic(object):
self.sm_doors = {}
self.prize_location = None
- def check_placement(self, unplaced_keys, big_key_loc=None, prize_loc=None, cr_count=7):
+ def check_placement(self, unplaced_keys, wild_keys, big_key_loc=None, prize_loc=None, cr_count=7):
for rule in self.placement_rules:
- if not rule.is_satisfiable(self.outside_keys, unplaced_keys, big_key_loc, prize_loc, cr_count):
+ if not rule.is_satisfiable(self.outside_keys, wild_keys, unplaced_keys, big_key_loc, prize_loc, cr_count):
return False
if big_key_loc:
for rule_a, rule_b in itertools.combinations(self.placement_rules, 2):
@@ -157,7 +158,7 @@ class PlacementRule(object):
left -= rule_needed
return False
- def is_satisfiable(self, outside_keys, unplaced_keys, big_key_loc, prize_location, cr_count):
+ def is_satisfiable(self, outside_keys, wild_keys, unplaced_keys, big_key_loc, prize_location, cr_count):
if self.prize_relevance and prize_location:
if self.prize_relevance == 'BigBomb':
if prize_location.item.name not in ['Crystal 5', 'Crystal 6']:
@@ -185,15 +186,20 @@ class PlacementRule(object):
if not bk_blocked and check_locations is None:
return True
available_keys = outside_keys
- empty_chests = 0
+ # todo: sometimes we need an extra empty chest to accomodate the big key too
+ # dungeon bias seed 563518200 for example
threshold = self.needed_keys_wo_bk if bk_blocked else self.needed_keys_w_bk
- for loc in check_locations:
- if not loc.item:
- empty_chests += 1
- elif loc.item and loc.item.name == self.small_key:
- available_keys += 1
- place_able_keys = min(empty_chests, unplaced_keys)
- available_keys += place_able_keys
+ if not wild_keys:
+ empty_chests = 0
+ for loc in check_locations:
+ if not loc.item:
+ empty_chests += 1
+ elif loc.item and loc.item.name == self.small_key:
+ available_keys += 1
+ place_able_keys = min(empty_chests, unplaced_keys)
+ available_keys += place_able_keys
+ else:
+ available_keys += unplaced_keys
return available_keys >= threshold
@@ -1100,40 +1106,30 @@ def location_is_bk_locked(loc, key_logic):
return loc in key_logic.bk_chests or loc in key_logic.bk_locked
-def prize_or_event(loc):
- return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
-
-
-def boss_unavail(loc, world, player):
- # todo: ambrosia
- # return world.bossdrops[player] == 'ambrosia' and "- Boss" in loc.name
- return False
-
-
-def blind_boss_unavail(loc, state, world, player):
- if loc.name == "Thieves' Town - Boss":
- # todo: check attic
- return (loc.parent_region.dungeon.boss.name == 'Blind' and
- (not any(x for x in state.found_locations if x.name == 'Suspicious Maiden') or
- (world.get_region('Thieves Attic Window', player).dungeon.name == 'Thieves Town' and
- not any(x for x in state.found_locations if x.name == 'Attic Cracked Floor'))))
- return False
+# todo: verfiy this code is defunct
+# def prize_or_event(loc):
+# return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
+#
+#
+# def reserved_location(loc, world, player):
+# return loc in world.item_pool.config.reserved_locations[player]
+#
+#
+# def blind_boss_unavail(loc, state, world, player):
+# if loc.name == "Thieves' Town - Boss":
+# return (loc.parent_region.dungeon.boss.name == 'Blind' and
+# (not any(x for x in state.found_locations if x.name == 'Suspicious Maiden') or
+# (world.get_region('Thieves Attic Window', player).dungeon.name == 'Thieves Town' and
+# not any(x for x in state.found_locations if x.name == 'Attic Cracked Floor'))))
+# return False
+# counts free locations for keys - hence why reserved locations don't count
def count_free_locations(state, world, player):
cnt = 0
for loc in state.found_locations:
- if (not prize_or_event(loc) and not loc.forced_item and not boss_unavail(loc, world, player)
- and not blind_boss_unavail(loc, state, world, player)):
- cnt += 1
- return cnt
-
-
-def count_locations_exclude_big_chest(state, world, player):
- cnt = 0
- for loc in state.found_locations:
- if ('- Big Chest' not in loc.name and not loc.forced_item and not boss_unavail(loc, world, player)
- and not prize_or_event(loc) and not blind_boss_unavail(loc, state, world, player)):
+ if (not prize_or_event(loc) and not loc.forced_item and not reserved_location(loc, world, player)
+ and not blind_boss_unavail(loc, state.found_locations, world, player)):
cnt += 1
return cnt
@@ -1396,9 +1392,9 @@ def forced_big_key_avail(locations):
return None
-def prize_relevance(key_layout, dungeon_entrance):
+def prize_relevance(key_layout, dungeon_entrance, is_atgt_swapped):
if len(key_layout.start_regions) > 1 and dungeon_entrance and dungeon_table[key_layout.key_logic.dungeon].prize:
- if dungeon_entrance.name in ['Ganons Tower', 'Inverted Ganons Tower']:
+ if dungeon_entrance.name == ('Agahnims Tower' if is_atgt_swapped else 'Ganons Tower'):
return 'GT'
elif dungeon_entrance.name == 'Pyramid Fairy':
return 'BigBomb'
@@ -1416,7 +1412,7 @@ def validate_key_layout(key_layout, world, player):
state.big_key_special = check_bk_special(key_layout.sector.regions, world, player)
for region in key_layout.start_regions:
dungeon_entrance, portal_door = find_outside_connection(region)
- prize_relevant_flag = prize_relevance(key_layout, dungeon_entrance)
+ prize_relevant_flag = prize_relevance(key_layout, dungeon_entrance, world.is_atgt_swapped(player))
if prize_relevant_flag:
state.append_door_to_list(portal_door, state.prize_doors)
state.prize_door_set[portal_door] = dungeon_entrance
@@ -1437,7 +1433,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
if state.big_key_opened:
ttl_locations = count_free_locations(state, world, player)
else:
- ttl_locations = count_locations_exclude_big_chest(state, world, player)
+ ttl_locations = count_locations_exclude_big_chest(state.found_locations, world, player)
ttl_small_key_only = count_small_key_only_locations(state)
available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_small_key_only, state, world, player)
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player)
@@ -1546,7 +1542,7 @@ def determine_prize_lock(key_layout, world, player):
prize_lock_possible = False
for region in key_layout.start_regions:
dungeon_entrance, portal_door = find_outside_connection(region)
- prize_relevant_flag = prize_relevance(key_layout, dungeon_entrance)
+ prize_relevant_flag = prize_relevance(key_layout, dungeon_entrance, world.is_atgt_swapped(player))
if prize_relevant_flag:
state.append_door_to_list(portal_door, state.prize_doors)
state.prize_door_set[portal_door] = dungeon_entrance
@@ -1616,7 +1612,7 @@ def create_key_counters(key_layout, world, player):
state.big_key_special = True
for region in key_layout.start_regions:
dungeon_entrance, portal_door = find_outside_connection(region)
- prize_relevant_flag = prize_relevance(key_layout, dungeon_entrance)
+ prize_relevant_flag = prize_relevance(key_layout, dungeon_entrance, world.is_atgt_swapped(player))
if prize_relevant_flag:
state.append_door_to_list(portal_door, state.prize_doors)
state.prize_door_set[portal_door] = dungeon_entrance
@@ -1663,7 +1659,7 @@ def can_open_door(door, state, world, player):
if state.big_key_opened:
ttl_locations = count_free_locations(state, world, player)
else:
- ttl_locations = count_locations_exclude_big_chest(state, world, player)
+ ttl_locations = count_locations_exclude_big_chest(state.found_locations, world, player)
if door.smallKey:
ttl_small_key_only = count_small_key_only_locations(state)
available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_small_key_only, state, world, player)
diff --git a/Main.py b/Main.py
index 23ef43d4..8cf913cd 100644
--- a/Main.py
+++ b/Main.py
@@ -1,4 +1,3 @@
-from collections import OrderedDict
import copy
from itertools import zip_longest
import json
@@ -14,23 +13,26 @@ from Bosses import place_bosses
from Items import ItemFactory
from KeyDoorShuffle import validate_key_placement
from OverworldGlitchRules import create_owg_connections
-from PotShuffle import shuffle_pots
+from PotShuffle import shuffle_pots, shuffle_pot_switches
from Regions import create_regions, create_shops, mark_light_world_regions, mark_dark_world_regions, create_dungeon_regions, adjust_locations
from OWEdges import create_owedges
from OverworldShuffle import link_overworld, update_world_regions, create_flute_exits
from EntranceShuffle import link_entrances
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, JsonRom, get_hash_string
from Doors import create_doors
-from DoorShuffle import link_doors, connect_portal
+from DoorShuffle import link_doors, connect_portal, link_doors_prep
from RoomData import create_rooms
from Rules import set_rules
-from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
-from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items
+from Dungeons import create_dungeons
+from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dungeons_restrictive, ensure_good_pots
from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations, set_prize_drops
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
from Utils import output_path, parse_player_names
-__version__ = '0.5.1.7-u'
+from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config
+from source.tools.BPS import create_bps_from_data
+
+__version__ = '1.0.1.3-u'
from source.classes.BabelFish import BabelFish
@@ -116,6 +118,7 @@ def main(args, seed=None, fish=None):
world.owKeepSimilar = args.ow_keepsimilar.copy()
world.owWhirlpoolShuffle = args.ow_whirlpool.copy()
world.owFluteShuffle = args.ow_fluteshuffle.copy()
+ world.shuffle_bonk_drops = args.bonk_drops.copy()
world.open_pyramid = args.openpyramid.copy()
world.boss_shuffle = args.shufflebosses.copy()
world.enemy_shuffle = args.shuffleenemies.copy()
@@ -125,16 +128,21 @@ def main(args, seed=None, fish=None):
world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)}
world.experimental = args.experimental.copy()
world.dungeon_counters = args.dungeon_counters.copy()
- world.potshuffle = args.shufflepots.copy()
world.fish = fish
world.shopsanity = args.shopsanity.copy()
- world.keydropshuffle = args.keydropshuffle.copy()
+ world.dropshuffle = args.dropshuffle.copy()
+ world.pottery = args.pottery.copy()
+ world.potshuffle = args.shufflepots.copy()
world.mixed_travel = args.mixed_travel.copy()
world.standardize_palettes = args.standardize_palettes.copy()
- world.treasure_hunt_count = {player: int(args.triforce_goal[player]) for player in range(1, world.players + 1)}
- world.treasure_hunt_total = {player: int(args.triforce_pool[player]) for player in range(1, world.players + 1)}
+ world.treasure_hunt_count = {k: int(v) for k, v in args.triforce_goal.items()}
+ world.treasure_hunt_total = {k: int(v) for k, v in args.triforce_pool.items()}
world.shufflelinks = args.shufflelinks.copy()
world.pseudoboots = args.pseudoboots.copy()
+ world.overworld_map = args.overworld_map.copy()
+ world.restrict_boss_items = args.restrict_boss_items.copy()
+ world.collection_rate = args.collection_rate.copy()
+ world.colorizepots = args.colorizepots.copy()
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
@@ -142,7 +150,7 @@ def main(args, seed=None, fish=None):
logger.info(
world.fish.translate("cli","cli","app.title") + "\n",
ORVersion,
- world.seed,
+ "%s (%s)" % (world.seed, str(args.outputname)) if str(args.outputname).startswith('M') else world.seed,
Settings.make_code(world, 1) if world.players == 1 else ''
)
@@ -158,10 +166,7 @@ def main(args, seed=None, fish=None):
world.player_names[player].append(name)
logger.info('')
- if world.owShuffle[1] != 'vanilla' or world.owCrossed[1] not in ['none', 'polar'] or world.owMixed[1] or str(args.outputname).startswith('M'):
- outfilebase = f'OR_{args.outputname if args.outputname else world.seed}'
- else:
- outfilebase = f'DR_{args.outputname if args.outputname else world.seed}'
+ outfilebase = f'OR_{args.outputname if args.outputname else world.seed}'
for player in range(1, world.players + 1):
world.difficulty_requirements[player] = difficulties[world.difficulty[player]]
@@ -170,21 +175,24 @@ def main(args, seed=None, fish=None):
if hasattr(world,"escape_assist") and player in world.escape_assist:
world.escape_assist[player].append('bombs') # enemized escape assumes infinite bombs available and will likely be unbeatable without it
- for tok in filter(None, args.startinventory[player].split(',')):
- item = ItemFactory(tok.replace("_", " ").strip(), player)
- if item:
- world.push_precollected(item)
-
+ if args.usestartinventory[player]:
+ for tok in filter(None, args.startinventory[player].split(',')):
+ item = ItemFactory(tok.replace("_", " ").strip(), player)
+ if item:
+ world.push_precollected(item)
+
if args.create_spoiler and not args.jsonout:
- logger.info(world.fish.translate("cli","cli","patching.spoiler"))
- world.spoiler.meta_to_file(output_path('%s_Spoiler.txt' % outfilebase))
+ logger.info(world.fish.translate("cli", "cli", "create.meta"))
+ world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
+ if args.mystery and not (args.suppress_meta or args.create_spoiler):
+ world.spoiler.mystery_meta_to_file(output_path(f'{outfilebase}_meta.txt'))
for player in range(1, world.players + 1):
create_regions(world, player)
- create_dungeon_regions(world, player)
- create_owedges(world, player)
if world.logic[player] in ('owglitches', 'nologic'):
create_owg_connections(world, player)
+ create_dungeon_regions(world, player)
+ create_owedges(world, player)
create_shops(world, player)
create_doors(world, player)
create_rooms(world, player)
@@ -196,7 +204,10 @@ def main(args, seed=None, fish=None):
logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))
for player in range(1, world.players + 1):
if world.potshuffle[player]:
- shuffle_pots(world, player)
+ if world.pottery[player] in ['none', 'cave', 'keys', 'cavekeys']:
+ shuffle_pots(world, player)
+ else:
+ shuffle_pot_switches(world, player)
logger.info(world.fish.translate("cli","cli","shuffling.overworld"))
@@ -211,7 +222,13 @@ def main(args, seed=None, fish=None):
for player in range(1, world.players + 1):
link_entrances(world, player)
- logger.info(world.fish.translate("cli","cli","shuffling.dungeons"))
+ logger.info(world.fish.translate("cli", "cli", "shuffling.prep"))
+ for player in range(1, world.players + 1):
+ link_doors_prep(world, player)
+
+ create_item_pool_config(world)
+
+ logger.info(world.fish.translate("cli", "cli", "shuffling.dungeons"))
for player in range(1, world.players + 1):
link_doors(world, player)
@@ -219,8 +236,7 @@ def main(args, seed=None, fish=None):
mark_light_world_regions(world, player)
else:
mark_dark_world_regions(world, player)
- logger.info(world.fish.translate("cli","cli","generating.itempool"))
- logger.info(world.fish.translate("cli","cli","generating.itempool"))
+ logger.info(world.fish.translate("cli", "cli", "generating.itempool"))
for player in range(1, world.players + 1):
generate_itempool(world, player)
@@ -230,6 +246,7 @@ def main(args, seed=None, fish=None):
for player in range(1, world.players + 1):
set_rules(world, player)
+ district_item_pool_config(world)
for player in range(1, world.players + 1):
if world.shopsanity[player]:
sell_potions(world, player)
@@ -241,7 +258,8 @@ def main(args, seed=None, fish=None):
for player in range(1, world.players + 1):
set_prize_drops(world, player)
- logger.info(world.fish.translate("cli","cli","placing.dungeon.prizes"))
+ massage_item_pool(world)
+ logger.info(world.fish.translate("cli", "cli", "placing.dungeon.prizes"))
fill_prizes(world)
@@ -250,14 +268,12 @@ def main(args, seed=None, fish=None):
logger.info(world.fish.translate("cli","cli","placing.dungeon.items"))
- shuffled_locations = None
- if args.algorithm in ['balanced', 'vt26'] or any(list(args.mapshuffle.values()) + list(args.compassshuffle.values()) +
- list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())):
+ if args.algorithm != 'equitable':
shuffled_locations = world.get_unfilled_locations()
random.shuffle(shuffled_locations)
fill_dungeons_restrictive(world, shuffled_locations)
else:
- fill_dungeons(world)
+ promote_dungeon_items(world)
for player in range(1, world.players+1):
if world.logic[player] != 'nologic':
@@ -275,39 +291,28 @@ def main(args, seed=None, fish=None):
logger.info(world.fish.translate("cli","cli","fill.world"))
- if args.algorithm == 'flood':
- flood_items(world) # different algo, biased towards early game progress items
- elif args.algorithm == 'vt21':
- distribute_items_cutoff(world, 1)
- elif args.algorithm == 'vt22':
- distribute_items_cutoff(world, 0.66)
- elif args.algorithm == 'freshness':
- distribute_items_staleness(world)
- elif args.algorithm == 'vt25':
- distribute_items_restrictive(world, False)
- elif args.algorithm == 'vt26':
-
- distribute_items_restrictive(world, True, shuffled_locations)
- elif args.algorithm == 'balanced':
- distribute_items_restrictive(world, True)
+ distribute_items_restrictive(world, True)
if world.players > 1:
- logger.info(world.fish.translate("cli","cli","balance.multiworld"))
- balance_multiworld_progression(world)
+ logger.info(world.fish.translate("cli", "cli", "balance.multiworld"))
+ if args.algorithm in ['balanced', 'equitable']:
+ balance_multiworld_progression(world)
# if we only check for beatable, we can do this sanity check first before creating the rom
if not world.can_beat_game(log_error=True):
- raise RuntimeError(world.fish.translate("cli","cli","cannot.beat.game"))
+ raise RuntimeError(world.fish.translate("cli", "cli", "cannot.beat.game"))
for player in range(1, world.players+1):
if world.shopsanity[player]:
customize_shops(world, player)
- balance_money_progression(world)
+ if args.algorithm in ['balanced', 'equitable']:
+ balance_money_progression(world)
+ ensure_good_pots(world, True)
rom_names = []
jsonout = {}
enemized = False
- if not args.suppress_rom:
+ if not args.suppress_rom or args.bps:
logger.info(world.fish.translate("cli","cli","patching.rom"))
for team in range(world.teams):
for player in range(1, world.players + 1):
@@ -333,7 +338,7 @@ def main(args, seed=None, fish=None):
logging.warning(enemizerMsg)
raise EnemizerError(enemizerMsg)
- patch_rom(world, rom, player, team, enemized, bool(args.outputname))
+ patch_rom(world, rom, player, team, enemized, bool(args.mystery))
if args.race:
patch_race_rom(rom)
@@ -344,7 +349,7 @@ def main(args, seed=None, fish=None):
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player],
args.fastmenu[player], args.disablemusic[player], args.sprite[player],
args.ow_palettes[player], args.uw_palettes[player], args.reduce_flashing[player],
- args.shuffle_sfx[player])
+ args.shuffle_sfx[player], args.msu_resume[player])
if args.jsonout:
jsonout[f'patch_t{team}_p{player}'] = rom.patches
@@ -355,7 +360,14 @@ def main(args, seed=None, fish=None):
if world.players > 1 or world.teams > 1:
outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else ''
outfilesuffix = f'_{Settings.make_code(world, player)}' if not args.outputname else ''
- rom.write_to_file(output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc'))
+ if args.bps:
+ patchfile = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.bps')
+ patch = create_bps_from_data(LocalRom(args.rom, patch=False).buffer, rom.buffer)
+ with open(patchfile, 'wb') as stream:
+ stream.write(patch.binary_ba)
+ if not args.suppress_rom:
+ sfc_file = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc')
+ rom.write_to_file(sfc_file)
if world.players > 1:
multidata = zlib.compress(json.dumps({"names": parsed_names,
@@ -371,9 +383,13 @@ def main(args, seed=None, fish=None):
with open(output_path('%s_multidata' % outfilebase), 'wb') as f:
f.write(multidata)
+ if args.mystery and not (args.suppress_meta or args.create_spoiler):
+ world.spoiler.hashes_to_file(output_path(f'{outfilebase}_meta.txt'))
+ elif args.create_spoiler and not args.jsonout:
+ world.spoiler.hashes_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
if args.create_spoiler and not args.jsonout:
- logger.info(world.fish.translate("cli","cli","patching.spoiler"))
- world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
+ logger.info(world.fish.translate("cli", "cli", "patching.spoiler"))
+ world.spoiler.to_file(output_path(f'{outfilebase}_Spoiler.txt'))
if not args.skip_playthrough:
logger.info(world.fish.translate("cli","cli","calc.playthrough"))
@@ -386,8 +402,8 @@ def main(args, seed=None, fish=None):
if args.jsonout:
with open(output_path('%s_Spoiler.json' % outfilebase), 'w') as outfile:
outfile.write(world.spoiler.to_json())
- else:
- world.spoiler.playthru_to_file(output_path('%s_Spoiler.txt' % outfilebase))
+ elif world.players > 1 or world.logic[1] != "nologic":
+ world.spoiler.playthrough_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
YES = world.fish.translate("cli","cli","yes")
NO = world.fish.translate("cli","cli","no")
@@ -449,6 +465,7 @@ def copy_world(world):
ret.owKeepSimilar = world.owKeepSimilar.copy()
ret.owWhirlpoolShuffle = world.owWhirlpoolShuffle.copy()
ret.owFluteShuffle = world.owFluteShuffle.copy()
+ ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy()
ret.open_pyramid = world.open_pyramid.copy()
ret.boss_shuffle = world.boss_shuffle.copy()
ret.enemy_shuffle = world.enemy_shuffle.copy()
@@ -458,14 +475,15 @@ def copy_world(world):
ret.intensity = world.intensity.copy()
ret.experimental = world.experimental.copy()
ret.shopsanity = world.shopsanity.copy()
- ret.keydropshuffle = world.keydropshuffle.copy()
+ ret.dropshuffle = world.dropshuffle.copy()
+ ret.pottery = world.pottery.copy()
+ ret.potshuffle = world.potshuffle.copy()
ret.mixed_travel = world.mixed_travel.copy()
ret.standardize_palettes = world.standardize_palettes.copy()
ret.owswaps = world.owswaps.copy()
ret.owflutespots = world.owflutespots.copy()
ret.prizes = world.prizes.copy()
-
- ret.exp_cache = world.exp_cache.copy()
+ ret.restrict_boss_items = world.restrict_boss_items.copy()
for player in range(1, world.players + 1):
create_regions(ret, player)
@@ -478,6 +496,9 @@ def copy_world(world):
if world.logic[player] in ('owglitches', 'nologic'):
create_owg_connections(ret, player)
+ # there are region references here they must be migrated to preserve integrity
+ # ret.exp_cache = world.exp_cache.copy()
+
copy_dynamic_regions_and_locations(world, ret)
for player in range(1, world.players + 1):
if world.mode[player] == 'standard':
@@ -526,6 +547,7 @@ def copy_world(world):
new_location.access_rule = lambda state: True
new_location.item_rule = lambda state: True
new_location.forced_item = location.forced_item
+ new_location.pot = location.pot
# copy remaining itempool. No item in itempool should have an assigned location
for item in world.itempool:
@@ -563,6 +585,128 @@ def copy_world(world):
return ret
+def copy_world_limited(world):
+ # ToDo: Not good yet
+ ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
+ world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
+ world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints)
+ ret.teams = world.teams
+ ret.player_names = copy.deepcopy(world.player_names)
+ ret.remote_items = world.remote_items.copy()
+ ret.required_medallions = world.required_medallions.copy()
+ ret.bottle_refills = world.bottle_refills.copy()
+ ret.swamp_patch_required = world.swamp_patch_required.copy()
+ ret.ganon_at_pyramid = world.ganon_at_pyramid.copy()
+ ret.powder_patch_required = world.powder_patch_required.copy()
+ ret.ganonstower_vanilla = world.ganonstower_vanilla.copy()
+ ret.treasure_hunt_count = world.treasure_hunt_count.copy()
+ ret.treasure_hunt_icon = world.treasure_hunt_icon.copy()
+ ret.sewer_light_cone = world.sewer_light_cone.copy()
+ ret.light_world_light_cone = world.light_world_light_cone
+ ret.dark_world_light_cone = world.dark_world_light_cone
+ ret.seed = world.seed
+ ret.can_access_trock_eyebridge = world.can_access_trock_eyebridge.copy()
+ ret.can_access_trock_front = world.can_access_trock_front.copy()
+ ret.can_access_trock_big_chest = world.can_access_trock_big_chest.copy()
+ ret.can_access_trock_middle = world.can_access_trock_middle.copy()
+ ret.can_take_damage = world.can_take_damage
+ ret.difficulty_requirements = world.difficulty_requirements.copy()
+ ret.fix_fake_world = world.fix_fake_world.copy()
+ ret.lamps_needed_for_dark_rooms = world.lamps_needed_for_dark_rooms
+ ret.mapshuffle = world.mapshuffle.copy()
+ ret.compassshuffle = world.compassshuffle.copy()
+ ret.keyshuffle = world.keyshuffle.copy()
+ ret.bigkeyshuffle = world.bigkeyshuffle.copy()
+ ret.bombbag = world.bombbag.copy()
+ ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy()
+ ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy()
+ ret.crystals_ganon_orig = world.crystals_ganon_orig.copy()
+ ret.crystals_gt_orig = world.crystals_gt_orig.copy()
+ ret.owKeepSimilar = world.owKeepSimilar.copy()
+ ret.owWhirlpoolShuffle = world.owWhirlpoolShuffle.copy()
+ ret.owFluteShuffle = world.owFluteShuffle.copy()
+ ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy()
+ ret.open_pyramid = world.open_pyramid.copy()
+ ret.boss_shuffle = world.boss_shuffle.copy()
+ ret.enemy_shuffle = world.enemy_shuffle.copy()
+ ret.enemy_health = world.enemy_health.copy()
+ ret.enemy_damage = world.enemy_damage.copy()
+ ret.beemizer = world.beemizer.copy()
+ ret.intensity = world.intensity.copy()
+ ret.experimental = world.experimental.copy()
+ ret.shopsanity = world.shopsanity.copy()
+ ret.dropshuffle = world.dropshuffle.copy()
+ ret.pottery = world.pottery.copy()
+ ret.potshuffle = world.potshuffle.copy()
+ ret.mixed_travel = world.mixed_travel.copy()
+ ret.standardize_palettes = world.standardize_palettes.copy()
+ ret.owswaps = world.owswaps.copy()
+ ret.owflutespots = world.owflutespots.copy()
+ ret.prizes = world.prizes.copy()
+ ret.restrict_boss_items = world.restrict_boss_items.copy()
+
+ ret.is_copied_world = True
+
+ for player in range(1, world.players + 1):
+ create_regions(ret, player)
+ update_world_regions(ret, player)
+ if world.logic[player] in ('owglitches', 'nologic'):
+ create_owg_connections(ret, player)
+ create_flute_exits(ret, player)
+ create_dungeon_regions(ret, player)
+ create_owedges(ret, player)
+ create_shops(ret, player)
+ create_doors(ret, player)
+ create_rooms(ret, player)
+ create_dungeons(ret, player)
+
+ for player in range(1, world.players + 1):
+ if world.mode[player] == 'standard':
+ parent = ret.get_region('Menu', player)
+ target = ret.get_region('Hyrule Castle Secret Entrance', player)
+ connection = Entrance(player, 'Uncle S&Q', parent)
+ parent.exits.append(connection)
+ connection.connect(target)
+
+ # connect copied world
+ copied_locations = {(loc.name, loc.player): loc for loc in ret.get_locations()} # caches all locations
+ for region in world.regions:
+ copied_region = ret.get_region(region.name, region.player)
+ copied_region.is_light_world = region.is_light_world
+ copied_region.is_dark_world = region.is_dark_world
+ copied_region.dungeon = region.dungeon
+ copied_region.locations = [copied_locations[(location.name, location.player)] for location in region.locations if (location.name, location.player) in copied_locations]
+ for location in copied_region.locations:
+ location.parent_region = copied_region
+ for entrance in region.entrances:
+ ret.get_entrance(entrance.name, entrance.player).connect(copied_region)
+
+ for item in world.precollected_items:
+ ret.push_precollected(ItemFactory(item.name, item.player))
+
+ for edge in world.owedges:
+ copiededge = ret.check_for_owedge(edge.name, edge.player)
+ if copiededge is not None:
+ copiededge.dest = ret.check_for_owedge(edge.dest.name, edge.dest.player)
+
+ for door in world.doors:
+ entrance = ret.check_for_entrance(door.name, door.player)
+ if entrance is not None:
+ destdoor = ret.check_for_door(entrance.door.name, entrance.door.player)
+ entrance.door = destdoor
+ if destdoor is not None:
+ destdoor.entrance = entrance
+
+ ret.key_logic = world.key_logic.copy()
+
+ from OverworldShuffle import categorize_world_regions
+ for player in range(1, world.players + 1):
+ categorize_world_regions(ret, player)
+ set_rules(ret, player)
+
+ return ret
+
+
def copy_dynamic_regions_and_locations(world, ret):
for region in world.dynamic_regions:
new_reg = Region(region.name, region.type, region.hint_text, region.player)
@@ -580,11 +724,7 @@ def copy_dynamic_regions_and_locations(world, ret):
for location in world.dynamic_locations:
new_reg = ret.get_region(location.parent_region.name, location.parent_region.player)
new_loc = Location(location.player, location.name, location.address, location.crystal, location.hint_text, new_reg)
- # todo: this is potentially dangerous. later refactor so we
- # can apply dynamic region rules on top of copied world like other rules
- new_loc.access_rule = location.access_rule
- new_loc.always_allow = location.always_allow
- new_loc.item_rule = location.item_rule
+ new_loc.type = location.type
new_reg.locations.append(new_loc)
ret.clear_location_cache()
@@ -597,7 +737,7 @@ def create_playthrough(world):
# get locations containing progress items
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement]
- optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop']
+ optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile']
state_cache = [None]
collection_spheres = []
state = CollectionState(world)
@@ -640,11 +780,11 @@ def create_playthrough(world):
# todo: this is not very efficient, but I'm not sure how else to do it for this backwards logic
# world.clear_exp_cache()
if world.can_beat_game(state_cache[num]):
- # logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is not required')
+ logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is not required')
to_delete.add(location)
else:
# still required, got to keep it around
- # logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is required')
+ logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is required')
location.item = old_item
# cull entries in spheres for spoiler walkthrough at end
@@ -703,9 +843,11 @@ def create_playthrough(world):
old_world.spoiler.paths = dict()
for player in range(1, world.players + 1):
- old_world.spoiler.paths.update({location.gen_name(): get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player})
+ if world.logic[player] != 'nologic':
+ old_world.spoiler.paths.update({location.gen_name(): get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player})
# we can finally output our playthrough
old_world.spoiler.playthrough = {"0": [str(item) for item in world.precollected_items if item.advancement]}
for i, sphere in enumerate(collection_spheres):
- old_world.spoiler.playthrough[str(i + 1)] = {location.gen_name(): str(location.item) for location in sphere}
+ if world.logic[player] != 'nologic':
+ old_world.spoiler.playthrough[str(i + 1)] = {location.gen_name(): str(location.item) for location in sphere}
diff --git a/MultiClient.py b/MultiClient.py
index 501630e0..fd0e8b4f 100644
--- a/MultiClient.py
+++ b/MultiClient.py
@@ -8,8 +8,10 @@ import shlex
import urllib.parse
import websockets
+from BaseClasses import PotItem, PotFlags
import Items
import Regions
+import PotShuffle
class ReceivedItem:
@@ -57,8 +59,13 @@ class Context:
self.key_drop_mode = False
self.shop_mode = False
self.retro_mode = False
+ self.pottery_mode = False
+ self.mystery_mode = False
self.ignore_count = 0
+ self.lookup_name_to_id = {}
+ self.lookup_id_to_name = {}
+
def color_code(*args):
codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
@@ -83,6 +90,12 @@ INGAME_MODES = {0x07, 0x09, 0x0b}
SAVEDATA_START = WRAM_START + 0xF000
SAVEDATA_SIZE = 0x500
+POT_ITEMS_SRAM_START = WRAM_START + 0x016018 # 2 bytes per room
+SPRITE_ITEMS_SRAM_START = WRAM_START + 0x016268 # 2 bytes per room
+SHOP_SRAM_START = WRAM_START + 0x0164B8 # 2 bytes?
+ITEM_SRAM_SIZE = 0x250
+SHOP_SRAM_LEN = 0x29 # 41 tracked items
+
RECV_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes
RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte
RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte
@@ -92,11 +105,9 @@ SCOUT_LOCATION_ADDR = SAVEDATA_START + 0x4D7 # 1 byte
SCOUTREPLY_LOCATION_ADDR = SAVEDATA_START + 0x4D8 # 1 byte
SCOUTREPLY_ITEM_ADDR = SAVEDATA_START + 0x4D9 # 1 byte
SCOUTREPLY_PLAYER_ADDR = SAVEDATA_START + 0x4DA # 1 byte
-SHOP_ADDR = SAVEDATA_START + 0x302 # 2 bytes?
DYNAMIC_TOTAL_ADDR = SAVEDATA_START + 0x33E # 2 bytes
MODE_FLAGS = SAVEDATA_START + 0x33D # 1 byte
-SHOP_SRAM_LEN = 0x29 # 41 tracked items
location_shop_order = [Regions.shop_to_location_table.keys()] + [Regions.retro_shops.keys()]
location_shop_ids = {0x0112, 0x0110, 0x010F, 0x00FF, 0x011F, 0x0109, 0x0115}
@@ -349,6 +360,8 @@ location_table_misc = {'Bottle Merchant': (0x3c9, 0x2),
'Purple Chest': (0x3c9, 0x10),
"Link's Uncle": (0x3c6, 0x1),
'Hobo': (0x3c9, 0x1)}
+location_table_pot_items = {}
+location_table_sprite_items = {}
SNES_DISCONNECTED = 0
SNES_CONNECTING = 1
@@ -676,7 +689,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
ctx.player_names = {p: n for p, n in args[1]}
msgs = []
if ctx.locations_checked:
- msgs.append(['LocationChecks', [Regions.lookup_name_to_id[loc] for loc in ctx.locations_checked]])
+ msgs.append(['LocationChecks', [ctx.lookup_name_to_id[loc] for loc in ctx.locations_checked]])
if ctx.locations_scouted:
msgs.append(['LocationScouts', list(ctx.locations_scouted)])
if msgs:
@@ -689,7 +702,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
elif start_index != len(ctx.items_received):
sync_msg = [['Sync']]
if ctx.locations_checked:
- sync_msg.append(['LocationChecks', [Regions.lookup_name_to_id[loc] for loc in ctx.locations_checked]])
+ sync_msg.append(['LocationChecks', [ctx.lookup_name_to_id[loc] for loc in ctx.locations_checked]])
await send_msgs(ctx.socket, sync_msg)
if start_index == len(ctx.items_received):
for item in items:
@@ -701,7 +714,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
if location not in ctx.locations_info:
replacements = {0xA2: 'Small Key', 0x9D: 'Big Key', 0x8D: 'Compass', 0x7D: 'Map'}
item_name = replacements.get(item, get_item_name_from_id(item))
- logging.info(f"Saw {color(item_name, 'red', 'bold')} at {list(Regions.lookup_id_to_name.keys())[location - 1]}")
+ logging.info(f"Saw {color(item_name, 'red', 'bold')} at {list(ctx.lookup_id_to_name.keys())[location - 1]}")
ctx.locations_info[location] = (item, player)
ctx.watcher_event.set()
@@ -710,7 +723,8 @@ async def process_server_cmd(ctx : Context, cmd, args):
item = color(get_item_name_from_id(item), 'cyan' if player_sent != ctx.slot else 'green')
player_sent = color(ctx.player_names[player_sent], 'yellow' if player_sent != ctx.slot else 'magenta')
player_recvd = color(ctx.player_names[player_recvd], 'yellow' if player_recvd != ctx.slot else 'magenta')
- logging.info('%s sent %s to %s (%s)' % (player_sent, item, player_recvd, get_location_name_from_address(location)))
+ location_name = get_location_name_from_address(ctx, location)
+ logging.info('%s sent %s to %s (%s)' % (player_sent, item, player_recvd, location_name))
if cmd == 'Print':
logging.info(args)
@@ -779,10 +793,10 @@ async def console_loop(ctx : Context):
for index, item in enumerate(ctx.items_received, 1):
logging.info('%s from %s (%s) (%d/%d in list)' % (
color(get_item_name_from_id(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
- get_location_name_from_address(item.location), index, len(ctx.items_received)))
+ get_location_name_from_address(ctx, item.location), index, len(ctx.items_received)))
if command[0] == '/missing':
- for location in [k for k, v in Regions.lookup_name_to_id.items()
+ for location in [k for k, v in ctx.lookup_name_to_id.items()
if type(v) is int and not filter_location(ctx, k)]:
if location not in ctx.locations_checked:
logging.info('Missing: ' + location)
@@ -804,15 +818,19 @@ def get_item_name_from_id(code):
return items[0] if items else f'Unknown item (ID:{code})'
-def get_location_name_from_address(address):
+def get_location_name_from_address(ctx, address):
if type(address) is str:
return address
- return Regions.lookup_id_to_name.get(address, f'Unknown location (ID:{address})')
+ return ctx.lookup_id_to_name.get(address, f'Unknown location (ID:{address})')
def filter_location(ctx, location):
- if not ctx.key_drop_mode and ('Key Drop' in location or 'Pot Key' in location):
+ if (not ctx.key_drop_mode and location in PotShuffle.key_drop_data
+ and PotShuffle.key_drop_data[location][0] == 'Drop'):
+ return True
+ if (not ctx.pottery_mode and location in PotShuffle.key_drop_data
+ and PotShuffle.key_drop_data[location][0] == 'Pot'):
return True
if not ctx.shop_mode and location in Regions.flat_normal_shops:
return True
@@ -821,6 +839,31 @@ def filter_location(ctx, location):
return False
+def init_lookups(ctx):
+ ctx.lookup_id_to_name = {x: y for x, y in Regions.lookup_id_to_name.items()}
+ ctx.lookup_name_to_id = {x: y for x, y in Regions.lookup_name_to_id.items()}
+ for location, datum in PotShuffle.key_drop_data.items():
+ type = datum[0]
+ if type == 'Drop':
+ location_id, super_tile, sprite_index = datum[1]
+ location_table_sprite_items[location] = (2 * super_tile, 0x8000 >> sprite_index)
+ ctx.lookup_name_to_id[location] = location_id
+ ctx.lookup_id_to_name[location_id] = location
+ for super_tile, pot_list in PotShuffle.vanilla_pots.items():
+ for pot_index, pot in enumerate(pot_list):
+ if pot.item != PotItem.Hole:
+ if pot.item == PotItem.Key:
+ loc_name = next(loc for loc, datum in PotShuffle.key_drop_data.items()
+ if datum[1] == super_tile)
+ else:
+ descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
+ loc_name = f'{pot.room} {descriptor}'
+ location_table_pot_items[loc_name] = (2 * super_tile, 0x8000 >> pot_index)
+ location_id = Regions.pot_address(pot_index, super_tile)
+ ctx.lookup_name_to_id[loc_name] = location_id
+ ctx.lookup_id_to_name[location_id] = loc_name
+
+
async def track_locations(ctx : Context, roomid, roomdata):
new_locations = []
@@ -832,9 +875,12 @@ async def track_locations(ctx : Context, roomid, roomdata):
if ctx.mode_flags is None:
flags = await snes_read(ctx, MODE_FLAGS, 1)
+ ctx.mode_flags = flags
ctx.key_drop_mode = flags[0] & 0x1
ctx.shop_mode = flags[0] & 0x2
ctx.retro_mode = flags[0] & 0x4
+ ctx.pottery_mode = flags[0] & 0x8
+ ctx.mystery_mode = flags[0] & 0x10
def new_check(location):
ctx.locations_checked.add(location)
@@ -842,12 +888,13 @@ async def track_locations(ctx : Context, roomid, roomdata):
if ignored:
ctx.ignore_count += 1
else:
- logging.info(f"New check: {location} ({len(ctx.locations_checked)-ctx.ignore_count}/{ctx.total_locations})")
- new_locations.append(Regions.lookup_name_to_id[location])
+ total = '???' if ctx.mystery_mode else ctx.total_locations
+ logging.info(f"New check: {location} ({len(ctx.locations_checked)-ctx.ignore_count}/{total})")
+ new_locations.append(ctx.lookup_name_to_id[location])
try:
if ctx.shop_mode or ctx.retro_mode:
- misc_data = await snes_read(ctx, SHOP_ADDR, SHOP_SRAM_LEN)
+ misc_data = await snes_read(ctx, SHOP_SRAM_START, SHOP_SRAM_LEN)
for cnt, b in enumerate(misc_data):
my_check = Regions.shop_table_by_location_id[0x400000 + cnt]
if int(b) > 0 and my_check not in ctx.locations_checked:
@@ -882,14 +929,22 @@ async def track_locations(ctx : Context, roomid, roomdata):
ow_unchecked = {}
for location, screenid in location_table_ow.items():
if location not in ctx.locations_checked:
- ow_unchecked[location] = screenid
+ ow_unchecked[location] = (screenid, 0x40)
+ ow_begin = min(ow_begin, screenid)
+ ow_end = max(ow_end, screenid + 1)
+ from Regions import bonk_prize_table
+ from OWEdges import OWTileRegions
+ for location, (_, flag, _, _, region_name, _) in bonk_prize_table.items():
+ if location not in ctx.locations_checked:
+ screenid = OWTileRegions[region_name]
+ ow_unchecked[location] = (screenid, flag)
ow_begin = min(ow_begin, screenid)
ow_end = max(ow_end, screenid + 1)
if ow_begin < ow_end:
ow_data = await snes_read(ctx, SAVEDATA_START + 0x280 + ow_begin, ow_end - ow_begin)
if ow_data is not None:
- for location, screenid in ow_unchecked.items():
- if ow_data[screenid - ow_begin] & 0x40 != 0:
+ for location, (screenid, flag) in ow_unchecked.items():
+ if ow_data[screenid - ow_begin] & flag != 0:
new_check(location)
if not all([location in ctx.locations_checked for location in location_table_npc.keys()]):
@@ -908,6 +963,22 @@ async def track_locations(ctx : Context, roomid, roomdata):
if misc_data[offset - 0x3c6] & mask != 0 and location not in ctx.locations_checked:
new_check(location)
+ if not all([location in ctx.locations_checked for location in location_table_pot_items.keys()]):
+ pot_items_data = await snes_read(ctx, POT_ITEMS_SRAM_START, ITEM_SRAM_SIZE)
+ if pot_items_data is not None:
+ for location, (offset, mask) in location_table_pot_items.items():
+ pot_value = pot_items_data[offset] | (pot_items_data[offset + 1] << 8)
+ if pot_value & mask != 0 and location not in ctx.locations_checked:
+ new_check(location)
+
+ if not all([location in ctx.locations_checked for location in location_table_sprite_items.keys()]):
+ sprite_items_data = await snes_read(ctx, SPRITE_ITEMS_SRAM_START, ITEM_SRAM_SIZE)
+ if sprite_items_data is not None:
+ for location, (offset, mask) in location_table_sprite_items.items():
+ sprite_value = sprite_items_data[offset] | (sprite_items_data[offset + 1] << 8)
+ if sprite_value & mask != 0 and location not in ctx.locations_checked:
+ new_check(location)
+
await send_msgs(ctx.socket, [['LocationChecks', new_locations]])
async def game_watcher(ctx : Context):
@@ -955,7 +1026,7 @@ async def game_watcher(ctx : Context):
item = ctx.items_received[recv_index]
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
color(get_item_name_from_id(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
- get_location_name_from_address(item.location), recv_index + 1, len(ctx.items_received)))
+ get_location_name_from_address(ctx, item.location), recv_index + 1, len(ctx.items_received)))
recv_index += 1
snes_buffered_write(ctx, RECV_PROGRESS_ADDR, bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
snes_buffered_write(ctx, RECV_ITEM_ADDR, bytes([item.item]))
@@ -969,7 +1040,7 @@ async def game_watcher(ctx : Context):
if scout_location > 0 and scout_location not in ctx.locations_scouted:
ctx.locations_scouted.add(scout_location)
- logging.info(f'Scouting item at {list(Regions.lookup_id_to_name.keys())[scout_location - 1]}')
+ logging.info(f'Scouting item at {list(ctx.lookup_id_to_name.keys())[scout_location - 1]}')
await send_msgs(ctx.socket, [['LocationScouts', [scout_location]]])
await track_locations(ctx, roomid, roomdata)
@@ -984,6 +1055,7 @@ async def main():
logging.basicConfig(format='%(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
ctx = Context(args.snes, args.connect, args.password)
+ init_lookups(ctx)
input_task = asyncio.create_task(console_loop(ctx))
diff --git a/MultiServer.py b/MultiServer.py
index cd333edf..4108b8dd 100644
--- a/MultiServer.py
+++ b/MultiServer.py
@@ -6,12 +6,15 @@ import json
import logging
import re
import shlex
+import ssl
import urllib.request
import websockets
import zlib
+from BaseClasses import PotItem, PotFlags
import Items
import Regions
+import PotShuffle
from MultiClient import ReceivedItem, get_item_name_from_id, get_location_name_from_address
class Client:
@@ -40,6 +43,9 @@ class Context:
self.clients = []
self.received_items = {}
+ self.lookup_name_to_id = {}
+ self.lookup_id_to_name = {}
+
async def send_msgs(websocket, msgs):
if not websocket or not websocket.open or websocket.closed:
return
@@ -154,8 +160,7 @@ def send_new_items(ctx : Context):
client.send_index = len(items)
def forfeit_player(ctx : Context, team, slot):
- all_locations = {values[0] for values in Regions.location_table.values() if type(values[0]) is int}
- all_locations.update({values[1] for values in Regions.key_drop_data.values()})
+ all_locations = set(ctx.lookup_id_to_name.keys())
notify_all(ctx, "%s (Team #%d) has forfeited" % (ctx.player_names[(team, slot)], team + 1))
register_location_checks(ctx, team, slot, all_locations)
@@ -176,7 +181,8 @@ def register_location_checks(ctx : Context, team, slot, locations):
recvd_items.append(new_item)
if slot != target_player:
broadcast_team(ctx, team, [['ItemSent', (slot, location, target_player, target_item)]])
- logging.info('(Team #%d) %s sent %s to %s (%s)' % (team+1, ctx.player_names[(team, slot)], get_item_name_from_id(target_item), ctx.player_names[(team, target_player)], get_location_name_from_address(location)))
+ loc_name = get_location_name_from_address(ctx, location)
+ logging.info('(Team #%d) %s sent %s to %s (%s)' % (team+1, ctx.player_names[(team, slot)], get_item_name_from_id(target_item), ctx.player_names[(team, target_player)], loc_name))
found_items = True
send_new_items(ctx)
@@ -249,11 +255,11 @@ async def process_client_cmd(ctx : Context, client : Client, cmd, args):
return
locs = []
for location in args:
- if type(location) is not int or 0 >= location > len(Regions.lookup_id_to_name.keys()):
+ if type(location) is not int or 0 >= location > len(ctx.lookup_id_to_name.keys()):
await send_msgs(client.socket, [['InvalidArguments', 'LocationScouts']])
return
- loc_name = list(Regions.lookup_id_to_name.keys())[location - 1]
- target_item, target_player = ctx.locations[(Regions.lookup_name_to_id[loc_name], client.slot)]
+ loc_name = list(ctx.lookup_id_to_name.keys())[location - 1]
+ target_item, target_player = ctx.locations[(ctx.lookup_name_to_id[loc_name], client.slot)]
replacements = {'SmallKey': 0xA2, 'BigKey': 0x9D, 'Compass': 0x8D, 'Map': 0x7D}
item_type = [i[2] for i in Items.item_table.values() if type(i[3]) is int and i[3] == target_item]
@@ -339,6 +345,30 @@ async def console(ctx : Context):
if command[0][0] != '/':
notify_all(ctx, '[Server]: ' + input)
+
+def init_lookups(ctx):
+ ctx.lookup_id_to_name = {x: y for x, y in Regions.lookup_id_to_name.items()}
+ ctx.lookup_name_to_id = {x: y for x, y in Regions.lookup_name_to_id.items()}
+ for location, datum in PotShuffle.key_drop_data.items():
+ type = datum[0]
+ if type == 'Drop':
+ location_id = datum[1][0]
+ ctx.lookup_name_to_id[location] = location_id
+ ctx.lookup_id_to_name[location_id] = location
+ for super_tile, pot_list in PotShuffle.vanilla_pots.items():
+ for pot_index, pot in enumerate(pot_list):
+ if pot.item != PotItem.Hole:
+ if pot.item == PotItem.Key:
+ loc_name = next(loc for loc, datum in PotShuffle.key_drop_data.items()
+ if datum[1] == super_tile)
+ else:
+ descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
+ loc_name = f'{pot.room} {descriptor}'
+ location_id = Regions.pot_address(pot_index, super_tile)
+ ctx.lookup_name_to_id[loc_name] = location_id
+ ctx.lookup_id_to_name[location_id] = loc_name
+
+
async def main():
parser = argparse.ArgumentParser()
parser.add_argument('--host', default=None)
@@ -353,7 +383,7 @@ async def main():
logging.basicConfig(format='[%(asctime)s] %(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
ctx = Context(args.host, args.port, args.password)
-
+ init_lookups(ctx)
ctx.data_filename = args.multidata
try:
@@ -376,7 +406,7 @@ async def main():
logging.error('Failed to read multiworld data (%s)' % e)
return
- ip = urllib.request.urlopen('https://v4.ident.me').read().decode('utf8') if not ctx.host else ctx.host
+ ip = urllib.request.urlopen('https://v4.ident.me', context=ssl._create_unverified_context()).read().decode('utf8') if not ctx.host else ctx.host
logging.info('Hosting game at %s:%d (%s)' % (ip, ctx.port, 'No password' if not ctx.password else 'Password: %s' % ctx.password))
ctx.disable_save = args.disable_save
diff --git a/Mystery.py b/Mystery.py
index c11db7d1..261accaf 100644
--- a/Mystery.py
+++ b/Mystery.py
@@ -1,5 +1,7 @@
import argparse
import logging
+from pathlib import Path
+import os
import RaceRandom as random
import urllib.request
import urllib.parse
@@ -29,6 +31,9 @@ def main():
parser.add_argument('--teams', default=1, type=lambda value: max(int(value), 1))
parser.add_argument('--create_spoiler', action='store_true')
parser.add_argument('--no_race', action='store_true')
+ parser.add_argument('--suppress_rom', action='store_true')
+ parser.add_argument('--suppress_meta', action='store_true')
+ parser.add_argument('--bps', action='store_true')
parser.add_argument('--rom')
parser.add_argument('--jsonout', action='store_true')
parser.add_argument('--enemizercli')
@@ -68,11 +73,15 @@ def main():
erargs.seed = seed
erargs.names = args.names
erargs.create_spoiler = args.create_spoiler
+ erargs.suppress_rom = args.suppress_rom
+ erargs.suppress_meta = args.suppress_meta
+ erargs.bps = args.bps
erargs.race = not args.no_race
erargs.outputname = seedname
if args.outputpath:
erargs.outputpath = args.outputpath
erargs.loglevel = args.loglevel
+ erargs.mystery = True
if args.rom:
erargs.rom = args.rom
@@ -81,6 +90,8 @@ def main():
if args.enemizercli:
erargs.enemizercli = args.enemizercli
+ mw_settings = {'algorithm': False}
+
settings_cache = {k: (roll_settings(v) if args.samesettings else None) for k, v in weights_cache.items()}
for player in range(1, args.multi + 1):
@@ -89,20 +100,23 @@ def main():
settings = settings_cache[path] if settings_cache[path] else roll_settings(weights_cache[path])
for k, v in vars(settings).items():
if v is not None:
- getattr(erargs, k)[player] = v
+ if k == 'algorithm': # multiworld wide parameters
+ if not mw_settings[k]: # only use the first roll
+ setattr(erargs, k, v)
+ mw_settings[k] = True
+ else:
+ getattr(erargs, k)[player] = v
else:
raise RuntimeError(f'No weights specified for player {player}')
DRMain(erargs, seed, BabelFish())
def get_weights(path):
- try:
- if urllib.parse.urlparse(path).scheme:
- return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)
- with open(path, 'r', encoding='utf-8') as f:
+ if os.path.exists(Path(path)):
+ with open(path, "r", encoding="utf-8") as f:
return yaml.load(f, Loader=yaml.SafeLoader)
- except Exception as e:
- raise Exception(f'Failed to read weights file: {e}')
+ elif urllib.parse.urlparse(path).scheme in ['http', 'https']:
+ return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)
def roll_settings(weights):
def get_choice(option, root=None):
@@ -133,12 +147,16 @@ def roll_settings(weights):
ret = argparse.Namespace()
+ ret.algorithm = get_choice('algorithm')
+
+ glitch_map = {'none': 'noglitches', 'no_logic': 'nologic', 'owglitches': 'owglitches',
+ 'owg': 'owglitches', 'minorglitches': 'minorglitches'}
glitches_required = get_choice('glitches_required')
if glitches_required is not None:
- if glitches_required not in ['none', 'owg', 'no_logic']:
- print("Only NMG, OWG, and No Logic supported")
+ if glitches_required not in glitch_map.keys():
+ print(f'Logic did not match one of: {", ".join(glitch_map.keys())}')
glitches_required = 'none'
- ret.logic = {'none': 'noglitches', 'owg': 'owglitches', 'no_logic': 'nologic'}[glitches_required]
+ ret.logic = glitch_map[glitches_required]
item_placement = get_choice('item_placement')
# not supported in ER
@@ -150,30 +168,39 @@ def roll_settings(weights):
ret.bigkeyshuffle = get_choice('bigkey_shuffle') == 'on' if 'bigkey_shuffle' in weights else dungeon_items in ['full']
ret.accessibility = get_choice('accessibility')
+ ret.restrict_boss_items = get_choice('restrict_boss_items')
overworld_shuffle = get_choice('overworld_shuffle')
ret.ow_shuffle = overworld_shuffle if overworld_shuffle != 'none' else 'vanilla'
+ valid_options = {'none', 'polar', 'grouped', 'limited', 'chaos'}
ret.ow_crossed = get_choice('overworld_crossed')
+ ret.ow_crossed = ret.ow_crossed if ret.ow_crossed in valid_options else 'none'
ret.ow_keepsimilar = get_choice('overworld_keepsimilar') == 'on'
ret.ow_mixed = get_choice('overworld_swap') == 'on'
ret.ow_whirlpool = get_choice('whirlpool_shuffle') == 'on'
overworld_flute = get_choice('flute_shuffle')
ret.ow_fluteshuffle = overworld_flute if overworld_flute != 'none' else 'vanilla'
+ ret.bonk_drops = get_choice('bonk_drops') == 'on'
entrance_shuffle = get_choice('entrance_shuffle')
ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'
+ overworld_map = get_choice('overworld_map')
+ ret.overworld_map = overworld_map if overworld_map != 'default' else 'default'
door_shuffle = get_choice('door_shuffle')
ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla'
ret.intensity = get_choice('intensity')
ret.experimental = get_choice('experimental') == 'on'
+ ret.collection_rate = get_choice('collection_rate') == 'on'
ret.dungeon_counters = get_choice('dungeon_counters') if 'dungeon_counters' in weights else 'default'
if ret.dungeon_counters == 'default':
ret.dungeon_counters = 'pickup' if ret.door_shuffle != 'vanilla' or ret.compassshuffle == 'on' else 'off'
- ret.shufflelinks = get_choice('shufflelinks') == 'on'
ret.pseudoboots = get_choice('pseudoboots') == 'on'
ret.shopsanity = get_choice('shopsanity') == 'on'
- ret.keydropshuffle = get_choice('keydropshuffle') == 'on'
+ ret.dropshuffle = get_choice('dropshuffle') == 'on'
+ ret.pottery = get_choice('pottery') if 'pottery' in weights else 'none'
+ ret.colorizepots = get_choice('colorizepots') == 'on'
+ ret.shufflepots = get_choice('pot_shuffle') == 'on'
ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent'
ret.standardize_palettes = get_choice('standardize_palettes') if 'standardize_palettes' in weights else 'standardize'
@@ -186,23 +213,26 @@ def roll_settings(weights):
'triforce-hunt': 'triforcehunt',
'trinity': 'trinity'
}[goal]
- ret.openpyramid = goal in ['fast_ganon', 'trinity'] if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False
+
+ ret.openpyramid = get_choice('open_pyramid') if 'open_pyramid' in weights else 'auto'
ret.shuffleganon = get_choice('shuffleganon') == 'on'
+ ret.shufflelinks = get_choice('shufflelinks') == 'on'
ret.crystals_gt = get_choice('tower_open')
-
ret.crystals_ganon = get_choice('ganon_open')
ganon_item = get_choice('ganon_item')
ret.ganon_item = ganon_item if ganon_item != 'none' else 'default'
- goal_min = get_choice_default('triforce_goal_min', default=20)
- goal_max = get_choice_default('triforce_goal_max', default=20)
- pool_min = get_choice_default('triforce_pool_min', default=30)
- pool_max = get_choice_default('triforce_pool_max', default=30)
+ from ItemList import set_default_triforce
+ default_tf_goal, default_tf_pool = set_default_triforce(ret.goal, 0, 0)
+ goal_min = get_choice_default('triforce_goal_min', default=default_tf_goal)
+ goal_max = get_choice_default('triforce_goal_max', default=default_tf_goal)
+ pool_min = get_choice_default('triforce_pool_min', default=default_tf_pool)
+ pool_max = get_choice_default('triforce_pool_max', default=default_tf_pool)
ret.triforce_goal = random.randint(int(goal_min), int(goal_max))
- min_diff = get_choice_default('triforce_min_difference', default=10)
+ min_diff = get_choice_default('triforce_min_difference', default=(default_tf_pool-default_tf_goal))
ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max))
ret.mode = get_choice('world_state')
@@ -252,8 +282,6 @@ def roll_settings(weights):
ret.enemy_health = get_choice('enemy_health')
- ret.shufflepots = get_choice('pot_shuffle') == 'on'
-
ret.beemizer = get_choice('beemizer') if 'beemizer' in weights else '0'
inventoryweights = weights.get('startinventory', {})
@@ -262,6 +290,8 @@ def roll_settings(weights):
if get_choice(item, inventoryweights) == 'on':
startitems.append(item)
ret.startinventory = ','.join(startitems)
+ if len(startitems) > 0:
+ ret.usestartinventory = True
if 'rom' in weights:
romweights = weights['rom']
@@ -269,6 +299,7 @@ def roll_settings(weights):
ret.disablemusic = get_choice('disablemusic', romweights) == 'on'
ret.quickswap = get_choice('quickswap', romweights) == 'on'
ret.reduce_flashing = get_choice('reduce_flashing', romweights) == 'on'
+ ret.msu_resume = get_choice('msu_resume', romweights) == 'on'
ret.fastmenu = get_choice('menuspeed', romweights)
ret.heartcolor = get_choice('heartcolor', romweights)
ret.heartbeep = get_choice('heartbeep', romweights)
diff --git a/OWEdges.py b/OWEdges.py
index 95c546ad..8c980601 100644
--- a/OWEdges.py
+++ b/OWEdges.py
@@ -38,7 +38,7 @@ NP = IsParallel.No
def create_owedges(world, player):
edges = [
# name, owID,dir,type,edge_id,(owSlot) vram
- create_owedge(player, 'Lost Woods NW', 0x00, No, Ld, 0x00) .coordInfo(0x00a0, 0x0284),
+ create_owedge(player, 'Lost Woods NW', 0x00, No, Ld, 0x00) .coordInfo(0x00a0, 0x0284).special_entrance(0x80),
create_owedge(player, 'Lost Woods SW', 0x00, So, Ld, 0x01, 0x08).coordInfo(0x0058, 0x2000),
create_owedge(player, 'Lost Woods SC', 0x00, So, Ld, 0x02, 0x08).coordInfo(0x0178, 0x2020),
create_owedge(player, 'Lost Woods SE', 0x00, So, Ld, 0x03, 0x09).coordInfo(0x0388, 0x2060),
@@ -53,7 +53,7 @@ def create_owedges(world, player):
create_owedge(player, 'Death Mountain TR Pegs WN', 0x07, We, Ld, 0x02) .coordInfo(0x0078, 0x00e0),
create_owedge(player, 'Mountain Entry NW', 0x0a, No, Ld, 0x01) .coordInfo(0x04cc, 0x180a),
create_owedge(player, 'Mountain Entry SE', 0x0a, So, Ld, 0x04) .coordInfo(0x0518, 0x1012),
- create_owedge(player, 'Zora Waterfall NE', 0x0f, No, Ld, 0x02) .coordInfo(0x0f80, 0x009a),
+ create_owedge(player, 'Zora Waterfall NE', 0x0f, No, Ld, 0x02) .coordInfo(0x0f80, 0x009a).special_entrance(0x82),
create_owedge(player, 'Zora Waterfall SE', 0x0f, So, Ld, 0x05) .coordInfo(0x0f80, 0x1020),
create_owedge(player, 'Lost Woods Pass NW', 0x10, No, Ld, 0x03) .coordInfo(0x0058, 0x1800),
create_owedge(player, 'Lost Woods Pass NE', 0x10, No, Ld, 0x04) .coordInfo(0x0178, 0x181e),
@@ -132,7 +132,7 @@ def create_owedges(world, player):
create_owedge(player, 'Links House ES', 0x2c, Ea, Ld, 0x17) .coordInfo(0x0b80, 0x08c0),
create_owedge(player, 'Stone Bridge NC', 0x2d, No, Ld, 0x14) .coordInfo(0x0af0, 0x180e),
create_owedge(player, 'Stone Bridge SC', 0x2d, So, Ld, 0x19) .coordInfo(0x0ae0, 0x100c),
- create_owedge(player, 'Stone Bridge WC', 0x2d, We, Wr, 0x17) .coordInfo(0x0b1c, 0x061c),
+ create_owedge(player, 'Stone Bridge WC', 0x2d, We, Wr, 0x17) .coordInfo(0x0b1c, 0x061c).special_entrance(0x81),
create_owedge(player, 'Stone Bridge WS', 0x2d, We, Ld, 0x18) .coordInfo(0x0b80, 0x08e0),
create_owedge(player, 'Stone Bridge EN', 0x2d, Ea, Ld, 0x18) .coordInfo(0x0a90, 0x01c0),
create_owedge(player, 'Stone Bridge EC', 0x2d, Ea, Wr, 0x19) .coordInfo(0x0b3c, 0x0640),
@@ -317,9 +317,9 @@ def create_owedges(world, player):
create_owedge(player, 'Bomber Corner NE', 0x7f, No, Ld, 0x41) .coordInfo(0x0f50, 0x181c),
create_owedge(player, 'Bomber Corner WC', 0x7f, We, Wr, 0x49) .coordInfo(0x0f30, 0x05e0),
create_owedge(player, 'Bomber Corner WS', 0x7f, We, Ld, 0x4a) .coordInfo(0x0f94, 0x0860),
- create_owedge(player, 'Master Sword Meadow SC', 0x80, So, Ld, 0x40) .coordInfo(0x0080, 0x0000),
- create_owedge(player, 'Hobo EC', 0x80, Ea, Wr, 0x4a) .coordInfo(0x008c, 0x0020),
- create_owedge(player, 'Zoras Domain SW', 0x81, So, Ld, 0x41, 0x89).coordInfo(0x02a4, 0x1782)
+ create_owedge(player, 'Master Sword Meadow SC', 0x80, So, Ld, 0x40) .coordInfo(0x0080, 0x0000).special_exit(0x80),
+ create_owedge(player, 'Hobo EC', 0x80, Ea, Wr, 0x4a) .coordInfo(0x008c, 0x0020).special_exit(0x81),
+ create_owedge(player, 'Zoras Domain SW', 0x81, So, Ld, 0x41, 0x89).coordInfo(0x02a4, 0x1782).special_exit(0x82)
]
world.owedges += edges
@@ -439,16 +439,16 @@ OWEdgeGroups = {
['Octoballoon NE']
]
),
- # (Op, LW, Vt, Ld, NP, 1): (
- # [
- # ['Master Sword Meadow SC'],
- # ['Zoras Domain SW']
- # ],
- # [
- # ['Lost Woods NW'],
- # ['Zora Waterfall NE']
- # ]
- # ),
+ (Op, LW, Vt, Ld, NP, 1): (
+ [
+ ['Master Sword Meadow SC'],
+ ['Zoras Domain SW']
+ ],
+ [
+ ['Lost Woods NW'],
+ ['Zora Waterfall NE']
+ ]
+ ),
(Op, LW, Hz, Ld, PL, 2): (
[
['Kakariko Fortune EN', 'Kakariko Fortune ES'],
@@ -505,14 +505,14 @@ OWEdgeGroups = {
['Statues WC']
]
),
- # (Op, LW, Hz, Wr, NP, 1): (
- # [
- # ['Hobo EC']
- # ],
- # [
- # ['Stone Bridge WC']
- # ]
- # ),
+ (Op, LW, Hz, Wr, NP, 1): (
+ [
+ ['Hobo EC']
+ ],
+ [
+ ['Stone Bridge WC']
+ ]
+ ),
(Op, LW, Vt, Wr, PL, 1): (
[
['Tree Line SC'],
@@ -627,6 +627,10 @@ OWEdgeGroups = {
['Hype Cave WN', 'Hype Cave WS']
]
),
+ (Op, DW, Vt, Ld, NP, 1): (
+ [ ],
+ [ ]
+ ),
(Op, DW, Hz, Ld, NP, 2): (
[
['Dig Game EC', 'Dig Game ES']
@@ -675,6 +679,10 @@ OWEdgeGroups = {
['Hype Cave WC']
]
),
+ (Op, DW, Hz, Wr, NP, 1): (
+ [ ],
+ [ ]
+ ),
(Op, DW, Vt, Wr, PL, 1): (
[
['Dark Tree Line SC'],
@@ -719,7 +727,7 @@ OWTileRegions = bidict({
'Zora Waterfall Area': 0x0f,
'Zora Waterfall Water': 0x0f,
- 'Waterfall of Wishing Cave': 0x0f,
+ 'Zora Waterfall Entryway': 0x0f,
'Lost Woods Pass West Area': 0x10,
'Lost Woods Pass East Top Area': 0x10,
@@ -976,289 +984,6 @@ OWTileRegions = bidict({
'Zoras Domain': 0x81
})
-OWTileGroups = {
- ("Woods", "Regular", "None"): (
- [
- 0x00, 0x2d, 0x80
- ],
- [
- 0x40, 0x6d
- ]
- ),
- ("Lumberjack", "Regular", "None"): (
- [
- 0x02
- ],
- [
- 0x42
- ]
- ),
- ("Mountain Entry", "Entrance", "None"): (
- [
- 0x03
- ],
- [
- 0x43
- ]
- ),
- ("East Mountain", "Regular", "None"): (
- [
- 0x05
- ],
- [
- 0x45
- ]
- ),
- ("East Mountain", "Entrance", "None"): (
- [
- 0x07
- ],
- [
- 0x47
- ]
- ),
- ("Lake", "Regular", "Zora"): (
- [
- 0x0f, 0x81
- ],
- [
- 0x4f
- ]
- ),
- ("Lake", "Regular", "Lake"): (
- [
- 0x35
- ],
- [
- 0x75
- ]
- ),
- ("Mountain Entry", "Regular", "None"): (
- [
- 0x0a
- ],
- [
- 0x4a
- ]
- ),
- ("Woods Pass", "Regular", "None"): (
- [
- 0x10
- ],
- [
- 0x50
- ]
- ),
- ("Fortune", "Regular", "None"): (
- [
- 0x11
- ],
- [
- 0x51
- ]
- ),
- ("Whirlpools", "Regular", "Pond"): (
- [
- 0x12
- ],
- [
- 0x52
- ]
- ),
- ("Whirlpools", "Regular", "Witch"): (
- [
- 0x15
- ],
- [
- 0x55
- ]
- ),
- ("Whirlpools", "Regular", "CWhirlpool"): (
- [
- 0x33
- ],
- [
- 0x73
- ]
- ),
- ("Whirlpools", "Regular", "Southeast"): (
- [
- 0x3f
- ],
- [
- 0x7f
- ]
- ),
- ("Castle", "Entrance", "None"): (
- [
- 0x13, 0x14
- ],
- [
- 0x53, 0x54
- ]
- ),
- ("Castle", "Regular", "None"): (
- [
- 0x1a, 0x1b
- ],
- [
- 0x5a, 0x5b
- ]
- ),
- ("Witch", "Regular", "None"): (
- [
- 0x16
- ],
- [
- 0x56
- ]
- ),
- ("Water Approach", "Regular", "None"): (
- [
- 0x17
- ],
- [
- 0x57
- ]
- ),
- ("Village", "Regular", "None"): (
- [
- 0x18
- ],
- [
- 0x58
- ]
- ),
- ("Wooden Bridge", "Regular", "None"): (
- [
- 0x1d
- ],
- [
- 0x5d
- ]
- ),
- ("Eastern", "Regular", "None"): (
- [
- 0x1e
- ],
- [
- 0x5e
- ]
- ),
- ("Blacksmith", "Regular", "None"): (
- [
- 0x22
- ],
- [
- 0x62
- ]
- ),
- ("Dunes", "Regular", "None"): (
- [
- 0x25
- ],
- [
- 0x65
- ]
- ),
- ("Game", "Regular", "None"): (
- [
- 0x28, 0x29
- ],
- [
- 0x68, 0x69
- ]
- ),
- ("Grove", "Regular", "None"): (
- [
- 0x2a
- ],
- [
- 0x6a
- ]
- ),
- ("Central Bonk Rocks", "Regular", "None"): (
- [
- 0x2b
- ],
- [
- 0x6b
- ]
- ),
- ("Links", "Regular", "None"): (
- [
- 0x2c
- ],
- [
- 0x6c
- ]
- ),
- ("Tree Line", "Regular", "None"): (
- [
- 0x2e
- ],
- [
- 0x6e
- ]
- ),
- ("Nook", "Regular", "None"): (
- [
- 0x2f
- ],
- [
- 0x6f
- ]
- ),
- ("Desert", "Regular", "None"): (
- [
- 0x30, 0x3a
- ],
- [
- 0x70, 0x7a
- ]
- ),
- ("Grove Approach", "Regular", "None"): (
- [
- 0x32
- ],
- [
- 0x72
- ]
- ),
- ("Hype", "Regular", "None"): (
- [
- 0x34
- ],
- [
- 0x74
- ]
- ),
- ("Shopping Mall", "Regular", "None"): (
- [
- 0x37
- ],
- [
- 0x77
- ]
- ),
- ("Swamp", "Regular", "None"): (
- [
- 0x3b
- ],
- [
- 0x7b
- ]
- ),
- ("South Pass", "Regular", "None"): (
- [
- 0x3c
- ],
- [
- 0x7c
- ]
- )
-}
-
parallel_links = bidict({'Lost Woods SW': 'Skull Woods SW',
'Lost Woods SC': 'Skull Woods SC',
'Lost Woods SE': 'Skull Woods SE',
@@ -1528,7 +1253,7 @@ OWExitTypes = {
'Mountain Entry Entrance Rock (West)',
'Mountain Entry Entrance Rock (East)',
'Zora Waterfall Water Entry',
- 'Waterfall of Wishing Cave Entry',
+ 'Zora Waterfall Water Approach',
'Zora Waterfall Landing',
'Lost Woods Pass Hammer (North)',
'Lost Woods Pass Hammer (South)',
@@ -1585,7 +1310,8 @@ OWExitTypes = {
'Lake Hylia West Pier',
'Lake Hylia Northeast Water Drop',
'Lake Hylia East Pier',
- 'Lake Hylia Water D Entry',
+ 'Lake Hylia Water D Approach',
+ 'Lake Hylia Water D Leave',
'Desert Pass Ladder (South)',
'Desert Pass Rocks (North)',
'Desert Pass Rocks (South)',
@@ -1854,6 +1580,7 @@ OWExitTypes = {
'South Shore East Mirror Spot',
'Lake Hylia Island Mirror Spot',
'Lake Hylia Water Mirror Spot',
+ 'Lake Hylia Water D Mirror Spot',
'Lake Hylia Central Island Mirror Spot',
'Ice Cave Mirror Spot',
'Desert Pass Ledge Mirror Spot',
diff --git a/OverworldShuffle.py b/OverworldShuffle.py
index b2dded81..6cac6e75 100644
--- a/OverworldShuffle.py
+++ b/OverworldShuffle.py
@@ -1,18 +1,21 @@
import RaceRandom as random, logging, copy
-from collections import OrderedDict
+from collections import OrderedDict, defaultdict
from DungeonGenerator import GenerationException
from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance
from Regions import mark_dark_world_regions, mark_light_world_regions
-from OWEdges import OWTileRegions, OWTileGroups, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel
+from OWEdges import OWTileRegions, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel
+from Utils import bidict
-__version__ = '0.2.6.1-u'
+version_number = '0.2.10.1'
+# branch indicator is intentionally different across branches
+version_branch = ''
+
+__version__ = '%s%s' % (version_number, version_branch)
def link_overworld(world, player):
# setup mandatory connections
for exitname, regionname in mandatory_connections:
connect_simple(world, exitname, regionname, player)
- for exitname, destname in temporary_mandatory_connections:
- connect_two_way(world, exitname, destname, player)
def performSwap(groups, swaps):
def getParallel(edgename):
@@ -101,8 +104,8 @@ def link_overworld(world, player):
else:
raise NotImplementedError('Invalid OW Edge swap scenario')
return new_groups
-
- tile_groups = reorganize_tile_groups(world, player)
+
+ tile_groups = define_tile_groups(world, player, False)
trimmed_groups = copy.deepcopy(OWEdgeGroups)
swapped_edges = list()
@@ -133,19 +136,20 @@ def link_overworld(world, player):
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][1].append(['Frog WC'])
trimmed_groups[group] = (forward_edges, back_edges)
+ connected_edges = []
+ if world.owShuffle[player] != 'vanilla':
+ trimmed_groups = remove_reserved(world, trimmed_groups, connected_edges, player)
+ trimmed_groups = reorganize_groups(world, trimmed_groups, player)
+
# tile shuffle
logging.getLogger('').debug('Swapping overworld tiles')
if world.owMixed[player]:
- swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], player)
-
- # move swapped regions/edges to other world
- trimmed_groups = performSwap(trimmed_groups, swapped_edges)
- assert len(swapped_edges) == 0, 'Not all edges were swapped successfully: ' + ', '.join(swapped_edges )
+ swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], False, player)
update_world_regions(world, player)
# update spoiler
- s = list(map(lambda x: ' ' if x not in world.owswaps[player][0] else 'S', [i for i in range(0x40)]))
+ s = list(map(lambda x: ' ' if x not in world.owswaps[player][0] else 'S', [i for i in range(0x40, 0x82)]))
text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
s[0x00], s[0x03], s[0x05],
s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f],
@@ -157,9 +161,9 @@ def link_overworld(world, player):
s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25],
s[0x3a],s[0x3b],s[0x3c], s[0x3f],
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f],
- s[0x32],s[0x33],s[0x34], s[0x37],
+ s[0x40], s[0x32],s[0x33],s[0x34], s[0x37],
s[0x30], s[0x35],
- s[0x3a],s[0x3b],s[0x3c], s[0x3f])
+ s[0x41], s[0x3a],s[0x3b],s[0x3c], s[0x3f])
world.spoiler.set_map('swaps', text_output, world.owswaps[player][0], player)
# apply tile logical connections
@@ -175,42 +179,77 @@ def link_overworld(world, player):
# crossed shuffle
logging.getLogger('').debug('Crossing overworld edges')
- if world.owCrossed[player] in ['grouped', 'limited', 'chaos']:
+ crossed_edges = list()
+
+ # more Maze Race/Suburb/Frog/Dig Game fixes
+ parallel_links_new = bidict(parallel_links) # shallow copy is enough (deep copy is broken)
+ if world.owKeepSimilar[player]:
+ del parallel_links_new['Maze Race ES']
+ del parallel_links_new['Kakariko Suburb WS']
+
+ #TODO: Revisit with changes to Limited/Allowed
+ if world.owCrossed[player] not in ['none', 'grouped', 'polar', 'chaos']:
+ for edge in swapped_edges:
+ if edge not in parallel_links_new and edge not in parallel_links_new.inverse:
+ crossed_edges.append(edge)
+
+ if world.owCrossed[player] in ['grouped', 'limited'] or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'chaos'):
if world.owCrossed[player] == 'grouped':
- ow_crossed_tiles = [[],[],[]]
- crossed_edges = shuffle_tiles(world, tile_groups, ow_crossed_tiles, player)
- elif world.owCrossed[player] in ['limited', 'chaos']:
- crossed_edges = list()
+ # the idea is to XOR the new swaps with the ones from Mixed so that non-parallel edges still work
+ # Polar corresponds to Grouped with no swaps in ow_crossed_tiles_mask
+ ow_crossed_tiles_mask = [[],[],[]]
+ crossed_edges = shuffle_tiles(world, define_tile_groups(world, player, True), ow_crossed_tiles_mask, True, player)
+ ow_crossed_tiles = [i for i in range(0x82) if (i in world.owswaps[player][0]) != (i in ow_crossed_tiles_mask[0])]
+
+ # update spoiler
+ s = list(map(lambda x: 'O' if x not in ow_crossed_tiles else 'X', [i for i in range(0x40, 0x82)]))
+ text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
+ s[0x00], s[0x03], s[0x05],
+ s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f],
+ s[0x0a], s[0x0f],
+ s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17],
+ s[0x18], s[0x1a],s[0x1b], s[0x1d],s[0x1e],
+ s[0x22], s[0x25], s[0x1a], s[0x1d],
+ s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x18], s[0x1b], s[0x1e],
+ s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25],
+ s[0x3a],s[0x3b],s[0x3c], s[0x3f],
+ s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f],
+ s[0x40], s[0x32],s[0x33],s[0x34], s[0x37],
+ s[0x30], s[0x35],
+ s[0x41], s[0x3a],s[0x3b],s[0x3c], s[0x3f])
+ world.spoiler.set_map('groups', text_output, ow_crossed_tiles, player)
+ else:
crossed_candidates = list()
for group in trimmed_groups.keys():
(mode, wrld, dir, terrain, parallel, count) = group
- if parallel == IsParallel.Yes and wrld == WorldType.Light and (mode == OpenStd.Open or world.mode[player] != 'standard'):
+ if wrld == WorldType.Light and mode != OpenStd.Standard:
for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]):
- if world.owKeepSimilar[player]:
- if world.owCrossed[player] == 'chaos' and random.randint(0, 1):
- for edge in forward_set:
- crossed_edges.append(edge)
- elif world.owCrossed[player] == 'limited':
- crossed_candidates.append(forward_set)
- else:
- for edge in forward_set:
+ if forward_set[0] in parallel_links_new or forward_set[0] in parallel_links_new.inverse:
+ if world.owKeepSimilar[player]:
if world.owCrossed[player] == 'chaos' and random.randint(0, 1):
- crossed_edges.append(edge)
+ for edge in forward_set:
+ crossed_edges.append(edge)
elif world.owCrossed[player] == 'limited':
- crossed_candidates.append([edge])
+ crossed_candidates.append(forward_set)
+ else:
+ for edge in forward_set:
+ if world.owCrossed[player] == 'chaos' and random.randint(0, 1):
+ crossed_edges.append(edge)
+ elif world.owCrossed[player] == 'limited':
+ crossed_candidates.append([edge])
if world.owCrossed[player] == 'limited':
random.shuffle(crossed_candidates)
for edge_set in crossed_candidates[:9]:
for edge in edge_set:
crossed_edges.append(edge)
for edge in copy.deepcopy(crossed_edges):
- if edge in parallel_links:
- crossed_edges.append(parallel_links[edge])
- elif edge in parallel_links.inverse:
- crossed_edges.append(parallel_links.inverse[edge][0])
-
- trimmed_groups = performSwap(trimmed_groups, crossed_edges)
- assert len(crossed_edges) == 0, 'Not all edges were crossed successfully: ' + ', '.join(crossed_edges)
+ if edge in parallel_links_new:
+ crossed_edges.append(parallel_links_new[edge])
+ elif edge in parallel_links_new.inverse:
+ crossed_edges.append(parallel_links_new.inverse[edge][0])
+
+ # after tile swap and crossed, determine edges that need to swap
+ edges_to_swap = [e for e in swapped_edges+crossed_edges if (e not in swapped_edges) or (e not in crossed_edges)]
# whirlpool shuffle
logging.getLogger('').debug('Shuffling whirlpools')
@@ -233,14 +272,14 @@ def link_overworld(world, player):
else:
if ((world.owCrossed[player] == 'none' or (world.owCrossed[player] == 'polar' and not world.owMixed[player])) and (world.get_region(from_region, player).type == RegionType.LightWorld)) \
or world.owCrossed[player] not in ['none', 'polar', 'grouped'] \
- or (world.owCrossed[player] == 'grouped' and ((from_owid < 0x40) == (from_owid not in ow_crossed_tiles[0]))):
+ or (world.owCrossed[player] == 'grouped' and ((world.get_region(from_region, player).type == RegionType.LightWorld) == (from_owid not in ow_crossed_tiles))):
whirlpool_candidates[0].append(tuple((from_owid, from_whirlpool, from_region)))
else:
whirlpool_candidates[1].append(tuple((from_owid, from_whirlpool, from_region)))
if ((world.owCrossed[player] == 'none' or (world.owCrossed[player] == 'polar' and not world.owMixed[player])) and (world.get_region(to_region, player).type == RegionType.LightWorld)) \
or world.owCrossed[player] not in ['none', 'polar', 'grouped'] \
- or (world.owCrossed[player] == 'grouped' and ((to_owid < 0x40) == (to_owid not in ow_crossed_tiles[0]))):
+ or (world.owCrossed[player] == 'grouped' and ((world.get_region(to_region, player).type == RegionType.LightWorld) == (to_owid not in ow_crossed_tiles))):
whirlpool_candidates[0].append(tuple((to_owid, to_whirlpool, to_region)))
else:
whirlpool_candidates[1].append(tuple((to_owid, to_whirlpool, to_region)))
@@ -250,6 +289,8 @@ def link_overworld(world, player):
for whirlpools in whirlpool_candidates:
random.shuffle(whirlpools)
while len(whirlpools):
+ if len(whirlpools) % 2 == 1:
+ x=0
from_owid, from_whirlpool, from_region = whirlpools.pop()
to_owid, to_whirlpool, to_region = whirlpools.pop()
connect_simple(world, from_whirlpool, to_region, player)
@@ -260,9 +301,12 @@ def link_overworld(world, player):
# layout shuffle
logging.getLogger('').debug('Shuffling overworld layout')
- connected_edges = []
if world.owShuffle[player] == 'vanilla':
+ # apply outstanding swaps
+ trimmed_groups = performSwap(trimmed_groups, edges_to_swap)
+ assert len(edges_to_swap) == 0, 'Not all edges were swapped successfully: ' + ', '.join(edges_to_swap)
+
# vanilla transitions
groups = list(trimmed_groups.values())
for (forward_edge_sets, back_edge_sets) in groups:
@@ -285,10 +329,9 @@ def link_overworld(world, player):
connect_custom(world, connected_edges, player)
# layout shuffle
- trimmed_groups = remove_reserved(world, trimmed_groups, connected_edges, player)
- groups = reorganize_groups(world, trimmed_groups, player)
+ groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player)
- tries = 10
+ tries = 100
valid_layout = False
connected_edge_cache = connected_edges.copy()
while not valid_layout and tries > 0:
@@ -359,7 +402,7 @@ def link_overworld(world, player):
new_spots = list()
ignored_regions = set()
- def addSpot(owid):
+ def addSpot(owid, ignore_proximity):
if world.owFluteShuffle[player] == 'balanced':
def getIgnored(regionname, base_owid, owid):
region = world.get_region(regionname, player)
@@ -379,15 +422,22 @@ def link_overworld(world, player):
new_ignored = {new_region}
getIgnored(new_region, OWTileRegions[new_region], OWTileRegions[new_region])
- if random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions):
+ if not ignore_proximity and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions):
return False
ignored_regions.update(new_ignored)
- flute_pool.remove(owid)
- new_spots.append(owid)
+ if owid in flute_pool:
+ flute_pool.remove(owid)
+ if ignore_proximity:
+ logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}')
+ logging.getLogger('').debug(f'Placing flute at: {hex(owid)}')
+ new_spots.append(owid)
+ else:
+ # TODO: Inspect later, seems to happen only with 'random' flute shuffle
+ logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}')
return True
# determine sectors (isolated groups of regions) to place flute spots
- flute_regions = {(f[0][0] if f[1] not in world.owswaps[player][0] else f[0][1]) : o for o, f in flute_data.items()}
+ flute_regions = {(f[0][0] if (f[1] not in world.owswaps[player][0]) != (world.mode[player] == 'inverted') else f[0][1]) : o for o, f in flute_data.items()}
flute_sectors = [(len([r for l in s for r in l]), [r for l in s for r in l if r in flute_regions]) for s in world.owsectors[player]]
flute_sectors = [s for s in flute_sectors if len(s[1]) > 0]
region_total = sum([c for c,_ in flute_sectors])
@@ -399,17 +449,22 @@ def link_overworld(world, player):
sector_total -= 1
spots_to_place = min(flute_spots - sector_total, max(1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5)))
target_spots = len(new_spots) + spots_to_place
+ logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)')
if 'Desert Palace Teleporter Ledge' in sector[1] or 'Misery Mire Teleporter Ledge' in sector[1]:
- addSpot(0x38) # guarantee desert/mire access
+ addSpot(0x38, False) # guarantee desert/mire access
random.shuffle(sector[1])
f = 0
+ t = 0
while len(new_spots) < target_spots:
if f >= len(sector[1]):
f = 0
+ t += 1
+ if t > 5:
+ raise GenerationException('Infinite loop detected in flute shuffle')
if sector[1][f] not in new_spots:
- addSpot(flute_regions[sector[1][f]])
+ addSpot(flute_regions[sector[1][f]], t > 0)
f += 1
region_total -= sector[0]
@@ -421,8 +476,9 @@ def link_overworld(world, player):
connect_flutes(new_spots)
# update spoiler
+ new_spots = list(map(lambda o: flute_data[o][1], new_spots))
s = list(map(lambda x: ' ' if x not in new_spots else 'F', [i for i in range(0x40)]))
- text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
+ text_output = flute_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
s[0x00], s[0x03], s[0x05],
s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f],
s[0x0a], s[0x0f],
@@ -498,33 +554,106 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None):
if not (parallel_forward_edge in connected_edges) and not (parallel_back_edge in connected_edges):
connect_two_way(world, parallel_forward_edge, parallel_back_edge, player, connected_edges)
except KeyError:
- # TODO: Figure out why non-parallel edges are getting into parallel groups
raise KeyError('No parallel edge for edge %s' % edgename2)
-def shuffle_tiles(world, groups, result_list, player):
+def shuffle_tiles(world, groups, result_list, do_grouped, player):
swapped_edges = list()
- valid_whirlpool_parity = False
+ group_parity = {}
+ for group_data in groups:
+ group = group_data[0]
+ parity = [0, 0, 0, 0, 0, 0]
+ # 0: vertical
+ if 0x00 in group:
+ parity[0] += 1
+ if 0x0f in group:
+ parity[0] += 1
+ if 0x80 in group:
+ parity[0] -= 1
+ if 0x81 in group:
+ parity[0] -= 1
+ # 1: horizontal land single
+ if 0x1a in group:
+ parity[1] -= 1
+ if 0x1b in group:
+ parity[1] += 1
+ if 0x28 in group:
+ parity[1] -= 1
+ if 0x29 in group:
+ parity[1] += 1
+ # 2: horizontal land double
+ if 0x28 in group:
+ parity[2] += 1
+ if 0x29 in group:
+ parity[2] -= 1
+ if 0x30 in group:
+ parity[2] -= 1
+ if 0x3a in group:
+ parity[2] += 1
+ # 3: horizontal water
+ if 0x2d in group:
+ parity[3] += 1
+ if 0x80 in group:
+ parity[3] -= 1
+ # 4: whirlpool
+ if 0x0f in group:
+ parity[4] += 1
+ if 0x12 in group:
+ parity[4] += 1
+ if 0x33 in group:
+ parity[4] += 1
+ if 0x35 in group:
+ parity[4] += 1
+ # 5: dropdown exit
+ for id in [0x00, 0x02, 0x13, 0x15, 0x18, 0x22]:
+ if id in group:
+ parity[5] += 1
+ if 0x1b in group and world.mode[player] != 'standard':
+ parity[5] += 1
+ if 0x1b in group and world.shuffle_ganon:
+ parity[5] -= 1
+ group_parity[group[0]] = parity
+
+ attempts = 1000
+ while True:
+ if attempts == 0: # expected to only occur with custom swaps
+ raise GenerationException('Could not find valid tile swaps')
- while not valid_whirlpool_parity:
# tile shuffle happens here
removed = list()
- for group in groups.keys():
- # if group[0] in ['Links', 'Central Bonk Rocks', 'Castle']: # TODO: Standard + Inverted
+ for group in groups:
+ #if 0x1b in group[0] or 0x13 in group[0] or (0x1a in group[0] and world.owCrossed[player] == 'none'): # TODO: Standard + Inverted
if random.randint(0, 1):
removed.append(group)
-
+
# save shuffled tiles to list
new_results = [[],[],[]]
- for group in groups.keys():
+ for group in groups:
if group not in removed:
- (owids, lw_regions, dw_regions) = groups[group]
+ (owids, lw_regions, dw_regions) = group
(exist_owids, exist_lw_regions, exist_dw_regions) = new_results
exist_owids.extend(owids)
exist_lw_regions.extend(lw_regions)
exist_dw_regions.extend(dw_regions)
- # check whirlpool parity
- valid_whirlpool_parity = world.owCrossed[player] not in ['none', 'grouped'] or len([o for o in new_results[0] if o in [0x0f, 0x12, 0x15, 0x33, 0x35, 0x3f, 0x55, 0x7f]]) % 2 == 0
+ parity = [sum(group_parity[group[0][0]][i] for group in groups if group not in removed) for i in range(6)]
+ if not world.owKeepSimilar[player]:
+ parity[1] += 2*parity[2]
+ parity[2] = 0
+ # if crossed terrain:
+ # parity[1] += parity[3]
+ # parity[3] = 0
+ parity[4] %= 2 # actual parity
+ if (world.owCrossed[player] == 'none' or do_grouped) and parity[:5] != [0, 0, 0, 0, 0]:
+ attempts -= 1
+ continue
+ # ensure sanc can be placed in LW in certain modes
+ if not do_grouped and world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lean', 'crossed', 'insanity'] and world.mode[player] != 'inverted' and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'):
+ free_dw_drops = parity[5] + (1 if world.shuffle_ganon else 0)
+ free_drops = 6 + (1 if world.mode[player] != 'standard' else 0) + (1 if world.shuffle_ganon else 0)
+ if free_dw_drops == free_drops:
+ attempts -= 1
+ continue
+ break
(exist_owids, exist_lw_regions, exist_dw_regions) = result_list
exist_owids.extend(new_results[0])
@@ -532,18 +661,13 @@ def shuffle_tiles(world, groups, result_list, player):
exist_dw_regions.extend(new_results[2])
# replace LW edges with DW
- ignore_list = list() #TODO: Remove ignore_list when special OW areas are included in pool
- for edgeset in temporary_mandatory_connections:
- for edge in edgeset:
- ignore_list.append(edge)
-
- if world.owCrossed[player] != 'polar':
+ if world.owCrossed[player] not in ['polar', 'grouped', 'chaos'] or do_grouped:
# in polar, the actual edge connections remain vanilla
def getSwappedEdges(world, lst, player):
for regionname in lst:
region = world.get_region(regionname, player)
for exit in region.exits:
- if exit.spot_type == 'OWEdge' and exit.name not in ignore_list:
+ if exit.spot_type == 'OWEdge':
swapped_edges.append(exit.name)
getSwappedEdges(world, result_list[1], player)
@@ -551,65 +675,75 @@ def shuffle_tiles(world, groups, result_list, player):
return swapped_edges
-def reorganize_tile_groups(world, player):
- groups = {}
- for (name, groupType, whirlpoolGroup) in OWTileGroups.keys():
- if world.mode[player] != 'standard' or name not in ['Castle', 'Links', 'Central Bonk Rocks'] \
- or (world.mode[player] == 'standard' and world.shuffle[player] in ['lean', 'crossed', 'insanity'] and name == 'Castle' and groupType == 'Entrance'):
- if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted']:
- if world.owWhirlpoolShuffle[player] or world.owCrossed[player] != 'none':
- groups[(name, whirlpoolGroup)] = ([], [], [])
- else:
- groups[(name,)] = ([], [], [])
- else:
- if world.owWhirlpoolShuffle[player] or world.owCrossed[player] != 'none':
- groups[(name, groupType, whirlpoolGroup)] = ([], [], [])
- else:
- groups[(name, groupType)] = ([], [], [])
+def define_tile_groups(world, player, do_grouped):
+ groups = [[i, i + 0x40] for i in range(0x40)]
- for (name, groupType, whirlpoolGroup) in OWTileGroups.keys():
- if world.mode[player] != 'standard' or name not in ['Castle', 'Links', 'Central Bonk Rocks'] \
- or (world.mode[player] == 'standard' and world.shuffle[player] in ['lean', 'crossed', 'insanity'] and name == 'Castle' and groupType == 'Entrance'):
- (lw_owids, dw_owids) = OWTileGroups[(name, groupType, whirlpoolGroup)]
- if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted']:
- if world.owWhirlpoolShuffle[player] or world.owCrossed[player] != 'none':
- (exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name, whirlpoolGroup)]
- exist_owids.extend(lw_owids)
- exist_owids.extend(dw_owids)
- for owid in lw_owids:
- exist_lw_regions.extend(OWTileRegions.inverse[owid])
- for owid in dw_owids:
- exist_dw_regions.extend(OWTileRegions.inverse[owid])
- groups[(name, whirlpoolGroup)] = (exist_owids, exist_lw_regions, exist_dw_regions)
- else:
- (exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name,)]
- exist_owids.extend(lw_owids)
- exist_owids.extend(dw_owids)
- for owid in lw_owids:
- exist_lw_regions.extend(OWTileRegions.inverse[owid])
- for owid in dw_owids:
- exist_dw_regions.extend(OWTileRegions.inverse[owid])
- groups[(name,)] = (exist_owids, exist_lw_regions, exist_dw_regions)
- else:
- if world.owWhirlpoolShuffle[player] or world.owCrossed[player] != 'none':
- (exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name, groupType, whirlpoolGroup)]
- exist_owids.extend(lw_owids)
- exist_owids.extend(dw_owids)
- for owid in lw_owids:
- exist_lw_regions.extend(OWTileRegions.inverse[owid])
- for owid in dw_owids:
- exist_dw_regions.extend(OWTileRegions.inverse[owid])
- groups[(name, groupType, whirlpoolGroup)] = (exist_owids, exist_lw_regions, exist_dw_regions)
- else:
- (exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name, groupType)]
- exist_owids.extend(lw_owids)
- exist_owids.extend(dw_owids)
- for owid in lw_owids:
- exist_lw_regions.extend(OWTileRegions.inverse[owid])
- for owid in dw_owids:
- exist_dw_regions.extend(OWTileRegions.inverse[owid])
- groups[(name, groupType)] = (exist_owids, exist_lw_regions, exist_dw_regions)
- return groups
+ def get_group(id):
+ for group in groups:
+ if id in group:
+ return group
+
+ def merge_groups(tile_links):
+ for link in tile_links:
+ merged_group = []
+ for id in link:
+ if id not in merged_group:
+ group = get_group(id)
+ groups.remove(group)
+ merged_group += group
+ groups.append(merged_group)
+
+ def can_shuffle_group(group):
+ # escape sequence should stay normal in standard
+ if world.mode[player] == 'standard' and (0x1b in group or 0x2b in group or 0x2c in group):
+ return False
+
+ # sanctuary/chapel should not be swapped if S+Q guaranteed to output on that screen
+ if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] \
+ and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3)) \
+ or (world.shuffle[player] in ['lite', 'lean'] and world.mode[player] == 'inverted')):
+ return False
+
+ return True
+
+ for i in [0x00, 0x03, 0x05, 0x18, 0x1b, 0x1e, 0x30, 0x35]:
+ groups.remove(get_group(i + 1))
+ groups.remove(get_group(i + 8))
+ groups.remove(get_group(i + 9))
+ groups.append([0x80])
+ groups.append([0x81])
+
+ if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple']:
+ merge_groups([[0x03, 0x0a], [0x28, 0x29]])
+
+ if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite']:
+ merge_groups([[0x13, 0x14]])
+
+ if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'simple', 'restricted']:
+ merge_groups([[0x05, 0x07]])
+
+ if world.shuffle[player] == 'vanilla' or (world.mode[player] == 'standard' and world.shuffle[player] in ['dungeonssimple', 'dungeonsfull']):
+ merge_groups([[0x13, 0x14, 0x1b]])
+
+ if world.owShuffle[player] == 'vanilla' and (world.owCrossed[player] == 'none' or do_grouped):
+ merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81], [0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]])
+
+ if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and (world.owCrossed[player] == 'none' or do_grouped):
+ merge_groups([[0x28, 0x29]])
+
+ if not world.owWhirlpoolShuffle[player] and (world.owCrossed[player] == 'none' or do_grouped):
+ merge_groups([[0x0f, 0x35], [0x12, 0x15, 0x33, 0x3f]])
+
+ tile_groups = []
+ for group in groups:
+ if can_shuffle_group(group):
+ lw_regions = []
+ dw_regions = []
+ for id in group:
+ (lw_regions if id < 0x40 or id >= 0x80 else dw_regions).extend(OWTileRegions.inverse[id])
+ tile_groups.append((group, lw_regions, dw_regions))
+
+ return tile_groups
def remove_reserved(world, groupedlist, connected_edges, player):
new_grouping = {}
@@ -617,7 +751,6 @@ def remove_reserved(world, groupedlist, connected_edges, player):
new_grouping[group] = ([], [])
for group in groupedlist.keys():
- (_, region, _, _, _, _) = group
(forward_edges, back_edges) = groupedlist[group]
# remove edges already connected (thru plando and other forced connections)
@@ -625,15 +758,6 @@ def remove_reserved(world, groupedlist, connected_edges, player):
forward_edges = list(list(filter((edge).__ne__, i)) for i in forward_edges)
back_edges = list(list(filter((edge).__ne__, i)) for i in back_edges)
- # remove parallel edges from pool, since they get added during shuffle
- if world.owShuffle[player] == 'parallel' and region == WorldType.Dark:
- for edge in parallel_links:
- forward_edges = list(list(filter((parallel_links[edge]).__ne__, i)) for i in forward_edges)
- back_edges = list(list(filter((parallel_links[edge]).__ne__, i)) for i in back_edges)
- for edge in parallel_links.inverse:
- forward_edges = list(list(filter((parallel_links.inverse[edge][0]).__ne__, i)) for i in forward_edges)
- back_edges = list(list(filter((parallel_links.inverse[edge][0]).__ne__, i)) for i in back_edges)
-
forward_edges = list(filter(([]).__ne__, forward_edges))
back_edges = list(filter(([]).__ne__, back_edges))
@@ -646,158 +770,56 @@ def remove_reserved(world, groupedlist, connected_edges, player):
return new_grouping
def reorganize_groups(world, groups, player):
+ def get_group_key(group):
+ #(std, region, axis, terrain, parallel, count) = group
+ new_group = list(group)
+ if world.mode[player] != "standard":
+ new_group[0] = None
+ if world.owShuffle[player] != 'parallel':
+ new_group[4] = None
+ if not world.owKeepSimilar[player]:
+ new_group[5] = None
+ return tuple(new_group)
+
# predefined shuffle groups get reorganized here
# this restructures the candidate pool based on the chosen settings
- if world.owShuffle[player] == 'full':
- if world.owKeepSimilar[player]:
- if world.mode[player] == 'standard':
- # tuple goes to (A,B,C,D,_,F)
- for grouping in (groups,):
- new_grouping = {}
+ for grouping in (groups,):
+ new_grouping = {}
- for group in grouping.keys():
- (std, region, axis, terrain, _, count) = group
- new_grouping[(std, region, axis, terrain, count)] = ([], [])
-
- for group in grouping.keys():
- (std, region, axis, terrain, _, count) = group
- (forward_edges, back_edges) = grouping[group]
- (exist_forward_edges, exist_back_edges) = new_grouping[(std, region, axis, terrain, count)]
- exist_forward_edges.extend(forward_edges)
- exist_back_edges.extend(back_edges)
- new_grouping[(std, region, axis, terrain, count)] = (exist_forward_edges, exist_back_edges)
+ for group in grouping.keys():
+ new_grouping[get_group_key(group)] = ([], [])
+
+ for group in grouping.keys():
+ new_group = get_group_key(group)
+ (forward_edges, back_edges) = grouping[group]
+ if not world.owKeepSimilar[player]:
+ forward_edges = [[i] for l in forward_edges for i in l]
+ back_edges = [[i] for l in back_edges for i in l]
+ (exist_forward_edges, exist_back_edges) = new_grouping[new_group]
+ exist_forward_edges.extend(forward_edges)
+ exist_back_edges.extend(back_edges)
+ new_grouping[new_group] = (exist_forward_edges, exist_back_edges)
- return list(new_grouping.values())
- else:
- # tuple goes to (_,B,C,D,_,F)
- for grouping in (groups,):
- new_grouping = {}
+ return new_grouping
- for group in grouping.keys():
- (_, region, axis, terrain, _, count) = group
- new_grouping[(region, axis, terrain, count)] = ([], [])
-
- for group in grouping.keys():
- (_, region, axis, terrain, _, count) = group
- (forward_edges, back_edges) = grouping[group]
- (exist_forward_edges, exist_back_edges) = new_grouping[(region, axis, terrain, count)]
- exist_forward_edges.extend(forward_edges)
- exist_back_edges.extend(back_edges)
- new_grouping[(region, axis, terrain, count)] = (exist_forward_edges, exist_back_edges)
-
- return list(new_grouping.values())
+def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player):
+ groups = defaultdict(lambda: ([],[]))
+ for (key, group) in trimmed_groups.items():
+ (mode, wrld, dir, terrain, parallel, count) = key
+ if mode == OpenStd.Standard:
+ groups[key] = group
else:
- if world.mode[player] == 'standard':
- # tuple goes to (A,B,C,D,_,_)
- for grouping in (groups,):
- new_grouping = {}
-
- for group in grouping.keys():
- (std, region, axis, terrain, _, _) = group
- new_grouping[(std, region, axis, terrain)] = ([], [])
-
- for group in grouping.keys():
- (std, region, axis, terrain, _, _) = group
- (forward_edges, back_edges) = grouping[group]
- forward_edges = [[i] for l in forward_edges for i in l]
- back_edges = [[i] for l in back_edges for i in l]
-
- (exist_forward_edges, exist_back_edges) = new_grouping[(std, region, axis, terrain)]
- exist_forward_edges.extend(forward_edges)
- exist_back_edges.extend(back_edges)
- new_grouping[(std, region, axis, terrain)] = (exist_forward_edges, exist_back_edges)
-
- return list(new_grouping.values())
+ if world.owCrossed[player] == 'chaos':
+ groups[(mode, None, dir, terrain, parallel, count)][0].extend(group[0])
+ groups[(mode, None, dir, terrain, parallel, count)][1].extend(group[1])
else:
- # tuple goes to (_,B,C,D,_,_)
- for grouping in (groups,):
- new_grouping = {}
-
- for group in grouping.keys():
- (_, region, axis, terrain, _, _) = group
- new_grouping[(region, axis, terrain)] = ([], [])
-
- for group in grouping.keys():
- (_, region, axis, terrain, _, _) = group
- (forward_edges, back_edges) = grouping[group]
- forward_edges = [[i] for l in forward_edges for i in l]
- back_edges = [[i] for l in back_edges for i in l]
-
- (exist_forward_edges, exist_back_edges) = new_grouping[(region, axis, terrain)]
- exist_forward_edges.extend(forward_edges)
- exist_back_edges.extend(back_edges)
- new_grouping[(region, axis, terrain)] = (exist_forward_edges, exist_back_edges)
-
- return list(new_grouping.values())
- elif world.owShuffle[player] == 'parallel':
- if world.owKeepSimilar[player]:
- if world.mode[player] == 'standard':
- # tuple stays (A,B,C,D,E,F)
- for grouping in (groups,):
- return list(grouping.values())
- else:
- # tuple goes to (_,B,C,D,E,F)
- for grouping in (groups,):
- new_grouping = {}
-
- for group in grouping.keys():
- (_, region, axis, terrain, parallel, count) = group
- new_grouping[(region, axis, terrain, parallel, count)] = ([], [])
-
- for group in grouping.keys():
- (_, region, axis, terrain, parallel, count) = group
- (forward_edges, back_edges) = grouping[group]
- (exist_forward_edges, exist_back_edges) = new_grouping[(region, axis, terrain, parallel, count)]
- exist_forward_edges.extend(forward_edges)
- exist_back_edges.extend(back_edges)
- new_grouping[(region, axis, terrain, parallel, count)] = (exist_forward_edges, exist_back_edges)
-
- return list(new_grouping.values())
- else:
- if world.mode[player] == 'standard':
- # tuple goes to (A,B,C,D,E,_)
- for grouping in (groups,):
- new_grouping = {}
-
- for group in grouping.keys():
- (std, region, axis, terrain, parallel, _) = group
- new_grouping[(std, region, axis, terrain, parallel)] = ([], [])
-
- for group in grouping.keys():
- (std, region, axis, terrain, parallel, _) = group
- (forward_edges, back_edges) = grouping[group]
- forward_edges = [[i] for l in forward_edges for i in l]
- back_edges = [[i] for l in back_edges for i in l]
-
- (exist_forward_edges, exist_back_edges) = new_grouping[(std, region, axis, terrain, parallel)]
- exist_forward_edges.extend(forward_edges)
- exist_back_edges.extend(back_edges)
- new_grouping[(std, region, axis, terrain, parallel)] = (exist_forward_edges, exist_back_edges)
-
- return list(new_grouping.values())
- else:
- # tuple goes to (_,B,C,D,E,_)
- for grouping in (groups,):
- new_grouping = {}
-
- for group in grouping.keys():
- (_, region, axis, terrain, parallel, _) = group
- new_grouping[(region, axis, terrain, parallel)] = ([], [])
-
- for group in grouping.keys():
- (_, region, axis, terrain, parallel, _) = group
- (forward_edges, back_edges) = grouping[group]
- forward_edges = [[i] for l in forward_edges for i in l]
- back_edges = [[i] for l in back_edges for i in l]
-
- (exist_forward_edges, exist_back_edges) = new_grouping[(region, axis, terrain, parallel)]
- exist_forward_edges.extend(forward_edges)
- exist_back_edges.extend(back_edges)
- new_grouping[(region, axis, terrain, parallel)] = (exist_forward_edges, exist_back_edges)
-
- return list(new_grouping.values())
- else:
- raise NotImplementedError('Shuffling not supported yet')
+ for i in range(2):
+ for edge_set in group[i]:
+ new_world = int(wrld)
+ if edge_set[0] in edges_to_swap:
+ new_world += 1
+ groups[(mode, WorldType(new_world % 2), dir, terrain, parallel, count)][i].append(edge_set)
+ return list(groups.values())
def create_flute_exits(world, player):
for region in (r for r in world.regions if r.player == player and r.terrain == Terrain.Land and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']):
@@ -876,14 +898,13 @@ def can_reach_smith(world, player):
return found
def build_sectors(world, player):
- from Main import copy_world
+ from Main import copy_world_limited
from OWEdges import OWTileRegions
# perform accessibility check on duplicate world
for p in range(1, world.players + 1):
world.key_logic[p] = {}
- base_world = copy_world(world)
- world.key_logic = {}
+ base_world = copy_world_limited(world)
# build lists of contiguous regions accessible with full inventory (excl portals/mirror/flute/entrances)
regions = list(OWTileRegions.copy().keys())
@@ -927,11 +948,23 @@ def build_sectors(world, player):
sectors2.append(explored_regions)
sectors[s] = sectors2
+ #TODO: Keep largest LW sector for Links House consideration, keep sector containing WDM for Old Man consideration
+ # sector_entrances = list()
+ # for sector in sectors:
+ # entrances = list()
+ # for s2 in sector:
+ # for region_name in s2:
+ # region = world.get_region(region_name, player)
+ # for exit in region.exits:
+ # if exit.spot_type == 'Entrance' and exit.name in entrance_pool:
+ # entrances.append(exit.name)
+ # sector_entrances.append(entrances)
+
return sectors
def build_accessible_region_list(world, start_region, player, build_copy_world=False, cross_world=False, region_rules=True, ignore_ledges = False):
- from Main import copy_world
from BaseClasses import CollectionState
+ from Main import copy_world_limited
from Items import ItemFactory
from Utils import stack_size3a
@@ -958,9 +991,8 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F
if build_copy_world:
for p in range(1, world.players + 1):
world.key_logic[p] = {}
- base_world = copy_world(world)
+ base_world = copy_world_limited(world)
base_world.override_bomb_check = True
- world.key_logic = {}
else:
base_world = world
@@ -974,7 +1006,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F
return explored_regions
def validate_layout(world, player):
- if world.accessibility[player] == 'beatable':
+ if world.accessibility[player] == 'none':
return True
entrance_connectors = {
@@ -1015,7 +1047,7 @@ def validate_layout(world, player):
entrance_connectors['Bumper Cave Entrance'] = ['West Dark Death Mountain (Bottom)']
entrance_connectors['Mountain Entry Entrance'] = ['Mountain Entry Ledge']
- from Main import copy_world
+ from Main import copy_world_limited
from Utils import stack_size3a
from EntranceShuffle import default_dungeon_connections, default_connector_connections, default_item_connections, default_shop_connections, default_drop_connections, default_dropexit_connections
@@ -1048,8 +1080,7 @@ def validate_layout(world, player):
for p in range(1, world.players + 1):
world.key_logic[p] = {}
- base_world = copy_world(world)
- world.key_logic = {}
+ base_world = copy_world_limited(world)
explored_regions = list()
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not world.shufflelinks[player]:
@@ -1080,12 +1111,10 @@ def validate_layout(world, player):
while unreachable_count != len(unreachable_regions):
# find unreachable regions
unreachable_regions = {}
- flat_sectors = [[r for l in s for r in l] for s in world.owsectors[player]]
- for sector in flat_sectors:
- for region_name in sector:
- if region_name not in explored_regions and region_name not in isolated_regions:
- region = base_world.get_region(region_name, player)
- unreachable_regions[region_name] = region
+ for region_name in list(OWTileRegions.copy().keys()):
+ if region_name not in explored_regions and region_name not in isolated_regions:
+ region = base_world.get_region(region_name, player)
+ unreachable_regions[region_name] = region
# loop thru unreachable regions to check if some can be excluded
unreachable_count = len(unreachable_regions)
@@ -1127,13 +1156,6 @@ test_connections = [
#('Links House NE', 'Lost Woods Pass SW')
]
-temporary_mandatory_connections = [
- # Special OW Areas
- ('Lost Woods NW', 'Master Sword Meadow SC'),
- ('Zora Waterfall NE', 'Zoras Domain SW'),
- ('Stone Bridge WC', 'Hobo EC'),
- ]
-
# these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions
mandatory_connections = [# Intra-tile OW Connections
('Lost Woods Bush (West)', 'Lost Woods East Area'), #pearl
@@ -1161,7 +1183,7 @@ mandatory_connections = [# Intra-tile OW Connections
('Zora Waterfall Landing', 'Zora Waterfall Area'),
('Zora Waterfall Water Drop', 'Zora Waterfall Water'), #flippers
('Zora Waterfall Water Entry', 'Zora Waterfall Water'), #flippers
- ('Waterfall of Wishing Cave Entry', 'Waterfall of Wishing Cave'), #flippers
+ ('Zora Waterfall Water Approach', 'Zora Waterfall Entryway'), #flippers
('Lost Woods Pass Hammer (North)', 'Lost Woods Pass Portal Area'), #hammer
('Lost Woods Pass Hammer (South)', 'Lost Woods Pass East Top Area'), #hammer
('Lost Woods Pass Rock (North)', 'Lost Woods Pass East Bottom Area'), #mitts
@@ -1230,7 +1252,8 @@ mandatory_connections = [# Intra-tile OW Connections
('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
('Lake Hylia West Pier', 'Lake Hylia Area'),
('Lake Hylia East Pier', 'Lake Hylia Northeast Bank'),
- ('Lake Hylia Water D Entry', 'Lake Hylia Water'), #flippers
+ ('Lake Hylia Water D Approach', 'Lake Hylia Water D'),
+ ('Lake Hylia Water D Leave', 'Lake Hylia Water'), #flippers
('Desert Pass Ledge Drop', 'Desert Pass Area'),
('Desert Pass Rocks (North)', 'Desert Pass Southeast'), #glove
('Desert Pass Rocks (South)', 'Desert Pass Area'), #glove
@@ -1689,6 +1712,7 @@ ow_connections = {
('Lake Hylia Island Mirror Spot', 'Lake Hylia Island'),
('Lake Hylia Central Island Mirror Spot', 'Lake Hylia Central Island'),
('Lake Hylia Water Mirror Spot', 'Lake Hylia Water'),
+ ('Lake Hylia Water D Mirror Spot', 'Lake Hylia Water D'),
('Lake Hylia Teleporter', 'Ice Palace Area')
], [
('Lake Hylia Island Pier', 'Lake Hylia Island'),
@@ -1737,14 +1761,14 @@ parallelsimilar_connections = [('Maze Race ES', 'Kakariko Suburb WS'),
]
# non shuffled overworld
-default_connections = [#('Lost Woods NW', 'Master Sword Meadow SC'),
+default_connections = [('Lost Woods NW', 'Master Sword Meadow SC'),
('Lost Woods SW', 'Lost Woods Pass NW'),
('Lost Woods SC', 'Lost Woods Pass NE'),
('Lost Woods SE', 'Kakariko Fortune NE'),
('Lost Woods EN', 'Lumberjack WN'),
('Lumberjack SW', 'Mountain Entry NW'),
('Mountain Entry SE', 'Kakariko Pond NE'),
- #('Zora Waterfall NE', 'Zoras Domain SW'),
+ ('Zora Waterfall NE', 'Zoras Domain SW'),
('Lost Woods Pass SW', 'Kakariko NW'),
('Lost Woods Pass SE', 'Kakariko NC'),
('Kakariko Fortune SC', 'Kakariko NE'),
@@ -1793,7 +1817,7 @@ default_connections = [#('Lost Woods NW', 'Master Sword Meadow SC'),
('Stone Bridge SC', 'Lake Hylia NW'),
('Stone Bridge EN', 'Tree Line WN'),
('Stone Bridge EC', 'Tree Line WC'),
- #('Stone Bridge WC', 'Hobo EC'),
+ ('Stone Bridge WC', 'Hobo EC'),
('Tree Line SC', 'Lake Hylia NC'),
('Tree Line SE', 'Lake Hylia NE'),
('Desert EC', 'Desert Pass WC'),
@@ -1955,6 +1979,26 @@ D(18)|s ss ss | +-+-+-+-+-+-+-+-+
E(20)| s s | D(18)| |s| |s| |
F(28)|ssssssss| | s +-+ s +-+ s |
G(30)|s ssss s| E(20)| |s| |s| |
+H(38)| sss s| +-+-+-+-+-+-+-+-+
+ +--------+ F(28)|s|s|s|s|s|s|s|s|
+ +-+ +-+-+-+-+-+-+-+-+
+ Ped/Hobo: |s| G(30)| |s|s|s| |s|
+ +-+ | s +-+-+-+ s +-+
+ Zora: |s| H(38)| |s|s|s| |s|
+ +-+ +---+-+-+-+---+-+"""
+
+flute_spoiler_table = \
+""" 0 1 2 3 4 5 6 7
+ +---+-+---+---+-+
+ 01234567 A(00)| |s| | |s|
+ +--------+ | s +-+ s | s +-+
+A(00)|s ss s s| B(08)| |s| | |s|
+B(08)| s s| +-+-+-+-+-+-+-+-+
+C(10)|ssssssss| C(10)|s|s|s|s|s|s|s|s|
+D(18)|s ss ss | +-+-+-+-+-+-+-+-+
+E(20)| s s | D(18)| |s| |s| |
+F(28)|ssssssss| | s +-+ s +-+ s |
+G(30)|s ssss s| E(20)| |s| |s| |
H(38)| sss s| +-+-+-+-+-+-+-+-+
+--------+ F(28)|s|s|s|s|s|s|s|s|
+-+-+-+-+-+-+-+-+
diff --git a/PotShuffle.py b/PotShuffle.py
index a0c048bf..7806f495 100644
--- a/PotShuffle.py
+++ b/PotShuffle.py
@@ -1,11 +1,15 @@
+import RaceRandom as random
+
from collections import defaultdict
-from BaseClasses import PotItem, Pot, PotFlags, CrystalBarrier
-from Regions import key_drop_data
+from BaseClasses import PotItem, Pot, PotFlags, CrystalBarrier, LocationType, RegionType
+from Utils import int16_as_bytes, pc_to_snes, snes_to_pc
+
+from source.dungeon.RoomObject import RoomObject, Shuffled_Pot
movable_switch_rooms = defaultdict(lambda: [],
{'PoD Stalfos Basement': ['PoD Basement Ledge'],
- 'Thieves Attic': ['Thieves Attic Hint'],
+ 'Thieves Attic Switch': ['Thieves Attic Hint'],
'Mire Hub Switch': ['Mire Hub', 'Mire Hub Right']})
invalid_key_rooms = {
@@ -18,269 +22,849 @@ invalid_key_rooms = {
}
vanilla_pots = {
- 2: [Pot(80, 6, PotItem.Nothing, 'Sewers Yet More Rats'), Pot(80, 8, PotItem.Nothing, 'Sewers Yet More Rats'), Pot(44, 8, PotItem.Nothing, 'Sewers Yet More Rats'), Pot(44, 10, PotItem.Nothing, 'Sewers Yet More Rats')],
- 4: [Pot(162, 25, PotItem.Nothing, 'TR Dash Room'), Pot(152, 25, PotItem.Nothing, 'TR Dash Room'), Pot(152, 22, PotItem.Nothing, 'TR Dash Room'), Pot(162, 22, PotItem.Nothing, 'TR Dash Room'), Pot(204, 19, PotItem.Bomb, 'TR Tongue Pull'),
- Pot(240, 19, PotItem.Bomb, 'TR Tongue Pull')],
- 9: [Pot(12, 4, PotItem.OneRupee, 'PoD Shooter Room'), Pot(48, 4, PotItem.Heart, 'PoD Shooter Room'), Pot(12, 12, PotItem.Switch, 'PoD Shooter Room')],
- 10: [Pot(96, 8, PotItem.Heart, 'PoD Stalfos Basement'), Pot(104, 8, PotItem.Heart, 'PoD Stalfos Basement'), Pot(204, 11, PotItem.Switch, 'PoD Stalfos Basement'), Pot(100, 9, PotItem.Nothing, 'PoD Stalfos Basement'),
- Pot(156, 17, PotItem.Bomb, 'PoD Basement Ledge', PotFlags.SwitchLogicChange), Pot(160, 17, PotItem.FiveArrows, 'PoD Basement Ledge', PotFlags.SwitchLogicChange)],
- 11: [Pot(202, 3, PotItem.Bomb, 'PoD Dark Pegs Left'), Pot(202, 12, PotItem.Bomb, 'PoD Dark Pegs Left')],
- 17: [Pot(152, 19, PotItem.Nothing, 'Sewers Secret Room'), Pot(152, 15, PotItem.Nothing, 'Sewers Secret Room'), Pot(144, 15, PotItem.Heart, 'Sewers Secret Room'), Pot(160, 15, PotItem.Heart, 'Sewers Secret Room'),
- Pot(144, 19, PotItem.Heart, 'Sewers Secret Room'), Pot(160, 19, PotItem.Heart, 'Sewers Secret Room')],
- 21: [Pot(96, 4, PotItem.Bomb, 'TR Pipe Pit'), Pot(100, 4, PotItem.SmallMagic, 'TR Pipe Pit'), Pot(104, 4, PotItem.Heart, 'TR Pipe Pit'), Pot(108, 4, PotItem.SmallMagic, 'TR Pipe Pit'), Pot(112, 4, PotItem.FiveArrows, 'TR Pipe Pit'),
- Pot(12, 6, PotItem.OneRupee, 'TR Pipe Pit'), Pot(16, 6, PotItem.FiveArrows, 'TR Pipe Pit'), Pot(20, 6, PotItem.FiveRupees, 'TR Pipe Pit'), Pot(70, 11, PotItem.BigMagic, 'TR Pipe Ledge')],
- 22: [Pot(188, 3, PotItem.Heart, 'Swamp I'), Pot(192, 3, PotItem.Heart, 'Swamp I'), Pot(188, 4, PotItem.SmallMagic, 'Swamp I'), Pot(192, 4, PotItem.SmallMagic, 'Swamp I'), Pot(188, 5, PotItem.FiveArrows, 'Swamp I'),
- Pot(192, 5, PotItem.FiveArrows, 'Swamp I'), Pot(188, 6, PotItem.Bomb, 'Swamp I'), Pot(192, 6, PotItem.Bomb, 'Swamp I'), Pot(240, 19, PotItem.Key, 'Swamp Waterway')],
- 23: [Pot(100, 13, PotItem.Heart, 'Hera 5F'), Pot(100, 14, PotItem.Heart, 'Hera 5F'), Pot(100, 15, PotItem.Heart, 'Hera 5F'), Pot(100, 16, PotItem.Heart, 'Hera 5F'), Pot(100, 17, PotItem.Heart, 'Hera 5F'), Pot(100, 18, PotItem.Heart, 'Hera 5F'),
- Pot(104, 13, PotItem.Heart, 'Hera 5F'), Pot(104, 14, PotItem.Heart, 'Hera 5F'), Pot(104, 15, PotItem.Heart, 'Hera 5F'), Pot(104, 16, PotItem.Heart, 'Hera 5F'), Pot(104, 17, PotItem.Heart, 'Hera 5F'), Pot(104, 18, PotItem.Heart, 'Hera 5F')],
- 26: [Pot(28, 5, PotItem.Bomb, 'PoD Falling Bridge Ledge'), Pot(32, 5, PotItem.Bomb, 'PoD Falling Bridge Ledge'), Pot(28, 27, PotItem.Bomb, 'PoD Falling Bridge'), Pot(32, 27, PotItem.Bomb, 'PoD Falling Bridge'),
- Pot(232, 19, PotItem.Nothing, 'PoD Harmless Hellway'), Pot(212, 19, PotItem.Nothing, 'PoD Harmless Hellway')],
- 27: [Pot(20, 23, PotItem.FiveArrows, 'PoD Mimics 2'), Pot(40, 23, PotItem.FiveArrows, 'PoD Mimics 2')],
- 30: [Pot(84, 9, PotItem.Bomb, 'Ice Bomb Drop')],
- 31: [Pot(28, 25, PotItem.Switch, 'Ice Pengator Switch'), Pot(28, 23, PotItem.Nothing, 'Ice Pengator Switch'), Pot(86, 26, PotItem.Nothing, 'Ice Big Key'), Pot(86, 27, PotItem.Nothing, 'Ice Big Key')],
- 33: [Pot(160, 20, PotItem.Nothing, 'Sewers Key Rat'), Pot(168, 24, PotItem.SmallMagic, 'Sewers Key Rat'), Pot(48, 28, PotItem.Heart, 'Sewers Key Rat'), Pot(82, 28, PotItem.SmallMagic, 'Sewers Key Rat'),
- Pot(100, 28, PotItem.Nothing, 'Sewers Key Rat'), Pot(104, 28, PotItem.Nothing, 'Sewers Key Rat')],
- 35: [Pot(86, 26, PotItem.OneRupee, 'TR Lazy Eyes'), Pot(90, 26, PotItem.Heart, 'TR Lazy Eyes'), Pot(94, 26, PotItem.OneRupee, 'TR Lazy Eyes'), Pot(98, 26, PotItem.Bomb, 'TR Lazy Eyes'), Pot(102, 26, PotItem.OneRupee, 'TR Lazy Eyes')],
- 36: [Pot(12, 4, PotItem.FiveRupees, 'TR Twin Pokeys'), Pot(48, 4, PotItem.Heart, 'TR Twin Pokeys'), Pot(12, 12, PotItem.SmallMagic, 'TR Twin Pokeys'), Pot(48, 12, PotItem.OneRupee, 'TR Twin Pokeys')],
- 38: [Pot(28, 4, PotItem.Bomb, 'Swamp Shooters'), Pot(12, 8, PotItem.SmallMagic, 'Swamp Shooters'), Pot(150, 19, PotItem.Switch, 'Swamp Push Statue'), Pot(22, 26, PotItem.FiveRupees, 'Swamp Push Statue'),
- Pot(220, 26, PotItem.FiveArrows, 'Swamp Push Statue', PotFlags.SwitchLogicChange)],
- 39: [Pot(214, 19, PotItem.Nothing, 'Hera 4F'), Pot(214, 20, PotItem.Nothing, 'Hera 4F'), Pot(166, 20, PotItem.Bomb, 'Hera 4F'), Pot(214, 21, PotItem.Heart, 'Hera 4F'), Pot(40, 28, PotItem.OneRupee, 'Hera 4F'),
- Pot(44, 28, PotItem.OneRupee, 'Hera 4F'), Pot(80, 28, PotItem.FiveRupees, 'Hera 4F'), Pot(84, 28, PotItem.FiveRupees, 'Hera 4F'), Pot(102, 17, PotItem.Nothing, 'Hera 4F'), Pot(98, 17, PotItem.Nothing, 'Hera 4F'),
- Pot(106, 17, PotItem.Nothing, 'Hera 4F'), Pot(166, 21, PotItem.Nothing, 'Hera 4F'), Pot(166, 19, PotItem.Nothing, 'Hera 4F'), Pot(92, 12, PotItem.Nothing, 'Hera 4F'), Pot(160, 12, PotItem.Nothing, 'Hera 4F')],
- 42: [Pot(80, 12, PotItem.OneRupee, 'PoD Arena Main'), Pot(80, 19, PotItem.Heart, 'PoD Arena Main')],
- 43: [Pot(16, 5, PotItem.Heart, 'PoD Sexy Statue'), Pot(44, 5, PotItem.Switch, 'PoD Sexy Statue'), Pot(16, 6, PotItem.Heart, 'PoD Sexy Statue'), Pot(44, 6, PotItem.Bomb, 'PoD Sexy Statue'), Pot(16, 7, PotItem.Heart, 'PoD Sexy Statue'),
- Pot(44, 7, PotItem.Bomb, 'PoD Sexy Statue'), Pot(146, 21, PotItem.Bomb, 'PoD Map Balcony'), Pot(170, 21, PotItem.FiveArrows, 'PoD Map Balcony'), Pot(146, 22, PotItem.Bomb, 'PoD Map Balcony'),
- Pot(170, 22, PotItem.FiveArrows, 'PoD Map Balcony')],
- 44: [Pot(108, 24, PotItem.Heart, 'Hookshot Cave (Middle)'), Pot(112, 24, PotItem.Heart, 'Hookshot Cave (Middle)')],
- 47: [Pot(28, 7, PotItem.Heart, 'Kakariko Well (top)'), Pot(32, 7, PotItem.Heart, 'Kakariko Well (top)'), Pot(28, 9, PotItem.FiveRupees, 'Kakariko Well (top)'), Pot(32, 9, PotItem.FiveRupees, 'Kakariko Well (top)'),
- Pot(172, 19, PotItem.FiveRupees, 'Kakariko Well (top)'), Pot(180, 19, PotItem.FiveRupees, 'Kakariko Well (top)'), Pot(104, 27, PotItem.Heart, 'Kakariko Well (bottom)'), Pot(104, 28, PotItem.Heart, 'Kakariko Well (bottom)')],
- 49: [Pot(92, 28, PotItem.Bomb, 'Hera Beetles'), Pot(96, 28, PotItem.Nothing, 'Hera Beetles')],
- 50: [Pot(28, 13, PotItem.SmallMagic, 'Sewers Dark Cross')],
- 52: [Pot(78, 8, PotItem.FiveRupees, 'Swamp Barrier Ledge'), Pot(92, 8, PotItem.FiveRupees, 'Swamp Barrier Ledge')],
- 53: [Pot(60, 6, PotItem.Key, 'Swamp Trench 2 Alcove'), Pot(20, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge'), Pot(24, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge'), Pot(28, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge'),
- Pot(32, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge'), Pot(36, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge'), Pot(48, 20, PotItem.Heart, 'Swamp Trench 2 Departure'), Pot(76, 23, PotItem.Nothing, 'Swamp Trench 2 Pots'),
- Pot(88, 23, PotItem.Nothing, 'Swamp Trench 2 Pots'), Pot(100, 27, PotItem.Nothing, 'Swamp Trench 2 Pots'), Pot(242, 28, PotItem.Nothing, 'Swamp Trench 2 Pots'), Pot(240, 22, PotItem.Heart, 'Swamp Trench 2 Pots'),
- Pot(76, 28, PotItem.Heart, 'Swamp Trench 2 Pots')],
- 54: [Pot(108, 4, PotItem.Bomb, 'Swamp Hub Dead Ledge'), Pot(112, 4, PotItem.FiveRupees, 'Swamp Hub Dead Ledge'), Pot(10, 16, PotItem.Heart, 'Swamp Hub'), Pot(154, 15, PotItem.Nothing, 'Swamp Hub'), Pot(114, 16, PotItem.Key, 'Swamp Hub'),
- Pot(222, 15, PotItem.Nothing, 'Swamp Hub'), Pot(188, 5, PotItem.Nothing, 'Swamp Hub North Ledge'), Pot(192, 5, PotItem.Nothing, 'Swamp Hub North Ledge')],
- 55: [Pot(60, 6, PotItem.Key, 'Swamp Trench 1 Alcove'), Pot(48, 20, PotItem.Nothing, 'Swamp Trench 1 Key Ledge')],
- 56: [Pot(164, 12, PotItem.Bomb, 'Swamp Pot Row'), Pot(164, 13, PotItem.FiveRupees, 'Swamp Pot Row'), Pot(164, 18, PotItem.Bomb, 'Swamp Pot Row'), Pot(164, 19, PotItem.Key, 'Swamp Pot Row')],
- 57: [Pot(12, 20, PotItem.Heart, 'Skull Spike Corner'), Pot(48, 28, PotItem.FiveArrows, 'Skull Spike Corner'), Pot(100, 22, PotItem.SmallMagic, 'Skull Final Drop'), Pot(100, 26, PotItem.FiveArrows, 'Skull Final Drop')],
- 60: [Pot(24, 8, PotItem.SmallMagic, 'Hookshot Cave (Front)'), Pot(64, 12, PotItem.FiveRupees, 'Hookshot Cave (Front)'), Pot(20, 14, PotItem.OneRupee, 'Hookshot Cave (Front)'), Pot(20, 19, PotItem.Nothing, 'Hookshot Cave (Front)'),
- Pot(68, 18, PotItem.FiveRupees, 'Hookshot Cave (Front)'), Pot(96, 19, PotItem.Heart, 'Hookshot Cave (Front)'), Pot(64, 20, PotItem.FiveRupees, 'Hookshot Cave (Front)'), Pot(64, 26, PotItem.FiveRupees, 'Hookshot Cave (Front)')],
- 61: [Pot(76, 12, PotItem.Bomb, 'GT Mini Helmasaur Room'), Pot(112, 12, PotItem.Bomb, 'GT Mini Helmasaur Room'), Pot(24, 22, PotItem.Heart, 'GT Crystal Inner Circle'), Pot(40, 22, PotItem.FiveArrows, 'GT Crystal Inner Circle'),
- Pot(32, 24, PotItem.Heart, 'GT Crystal Inner Circle'), Pot(20, 26, PotItem.FiveRupees, 'GT Crystal Inner Circle'), Pot(36, 26, PotItem.BigMagic, 'GT Crystal Inner Circle')],
- 62: [Pot(96, 6, PotItem.Bomb, 'Ice Stalfos Hint'), Pot(100, 6, PotItem.SmallMagic, 'Ice Stalfos Hint'), Pot(88, 10, PotItem.Heart, 'Ice Stalfos Hint'), Pot(92, 10, PotItem.SmallMagic, 'Ice Stalfos Hint')],
- 63: [Pot(12, 25, PotItem.OneRupee, 'Ice Hammer Block'), Pot(20, 25, PotItem.OneRupee, 'Ice Hammer Block'), Pot(12, 26, PotItem.Bomb, 'Ice Hammer Block'), Pot(20, 26, PotItem.Bomb, 'Ice Hammer Block'),
- Pot(12, 27, PotItem.Switch, 'Ice Hammer Block'), Pot(20, 27, PotItem.Heart, 'Ice Hammer Block'), Pot(28, 23, PotItem.Key, 'Ice Hammer Block')],
- 65: [Pot(100, 10, PotItem.Heart, 'Sewers Behind Tapestry'), Pot(52, 15, PotItem.OneRupee, 'Sewers Behind Tapestry'), Pot(52, 16, PotItem.SmallMagic, 'Sewers Behind Tapestry'), Pot(148, 22, PotItem.SmallMagic, 'Sewers Behind Tapestry')],
- 67: [Pot(66, 4, PotItem.FiveArrows, 'Desert Wall Slide'), Pot(78, 4, PotItem.SmallMagic, 'Desert Wall Slide'), Pot(66, 9, PotItem.Heart, 'Desert Wall Slide'), Pot(78, 9, PotItem.Heart, 'Desert Wall Slide'),
- Pot(112, 28, PotItem.Nothing, 'Desert Tiles 2'), Pot(76, 28, PotItem.Nothing, 'Desert Tiles 2'), Pot(76, 20, PotItem.Nothing, 'Desert Tiles 2'), Pot(112, 20, PotItem.Key, 'Desert Tiles 2')],
- 69: [Pot(12, 4, PotItem.FiveArrows, 'Thieves Basement Block'), Pot(48, 12, PotItem.FiveArrows, 'Thieves Basement Block'), Pot(92, 11, PotItem.Nothing, "Thieves Blind's Cell"), Pot(108, 11, PotItem.Heart, "Thieves Blind's Cell"),
- Pot(220, 16, PotItem.SmallMagic, "Thieves Blind's Cell"), Pot(236, 16, PotItem.Heart, "Thieves Blind's Cell")],
- 70: [Pot(96, 5, PotItem.Heart, 'Swamp Donut Top'), Pot(28, 27, PotItem.Heart, 'Swamp Donut Bottom')],
- 73: [Pot(104, 15, PotItem.SmallMagic, 'Skull Torch Room'), Pot(104, 16, PotItem.SmallMagic, 'Skull Torch Room'), Pot(156, 27, PotItem.Nothing, 'Skull Star Pits'), Pot(172, 24, PotItem.Nothing, 'Skull Star Pits'),
- Pot(172, 23, PotItem.Nothing, 'Skull Star Pits'), Pot(144, 20, PotItem.Nothing, 'Skull Star Pits'), Pot(144, 19, PotItem.SmallMagic, 'Skull Star Pits'), Pot(172, 20, PotItem.Heart, 'Skull Star Pits'),
- Pot(144, 27, PotItem.Heart, 'Skull Star Pits'), Pot(172, 28, PotItem.SmallMagic, 'Skull Star Pits'), Pot(160, 27, PotItem.Nothing, 'Skull Star Pits')],
- 74: [Pot(14, 5, PotItem.Switch, 'PoD Left Cage'), Pot(32, 5, PotItem.Bomb, 'PoD Left Cage'), Pot(14, 11, PotItem.Heart, 'PoD Left Cage'), Pot(32, 11, PotItem.OneRupee, 'PoD Left Cage'), Pot(56, 8, PotItem.Bomb, 'PoD Middle Cage'),
- Pot(68, 8, PotItem.Bomb, 'PoD Middle Cage'), Pot(92, 5, PotItem.Bomb, 'PoD Middle Cage'), Pot(110, 5, PotItem.Switch, 'PoD Middle Cage'), Pot(92, 11, PotItem.OneRupee, 'PoD Middle Cage'), Pot(110, 11, PotItem.Heart, 'PoD Middle Cage')],
- 75: [Pot(20, 6, PotItem.FiveArrows, 'PoD Mimics 1'), Pot(40, 6, PotItem.Heart, 'PoD Mimics 1')],
- 78: [Pot(140, 7, PotItem.Nothing, 'Ice Bomb Jump Catwalk'), Pot(48, 10, PotItem.Nothing, 'Ice Bomb Jump Catwalk'), Pot(140, 11, PotItem.Switch, 'Ice Bomb Jump Catwalk'), Pot(28, 12, PotItem.Heart, 'Ice Bomb Jump Catwalk'),
- Pot(112, 12, PotItem.SmallMagic, 'Ice Narrow Corridor')],
- 80: [Pot(96, 38, PotItem.Heart, 'Hyrule Castle West Hall'), Pot(100, 38, PotItem.Heart, 'Hyrule Castle West Hall')],
- 82: [Pot(138, 3, PotItem.Heart, 'Hyrule Castle East Hall'), Pot(194, 26, PotItem.Heart, 'Hyrule Castle East Hall')],
- 83: [Pot(92, 11, PotItem.Heart, 'Desert Beamos Hall'), Pot(96, 11, PotItem.SmallMagic, 'Desert Beamos Hall'), Pot(100, 11, PotItem.Key, 'Desert Beamos Hall'), Pot(104, 11, PotItem.Heart, 'Desert Beamos Hall')],
- 84: [Pot(186, 25, PotItem.FiveRupees, 'Swamp Attic'), Pot(186, 26, PotItem.Heart, 'Swamp Attic'), Pot(186, 27, PotItem.Heart, 'Swamp Attic'), Pot(186, 28, PotItem.Heart, 'Swamp Attic')],
- 85: [Pot(230, 24, PotItem.SmallMagic, 'Secret Passage'), Pot(230, 25, PotItem.SmallMagic, 'Secret Passage')],
- 86: [Pot(100, 6, PotItem.Nothing, 'Skull Back Drop'), Pot(96, 10, PotItem.Nothing, 'Skull Back Drop'), Pot(92, 10, PotItem.Nothing, 'Skull Back Drop'), Pot(20, 6, PotItem.SmallMagic, 'Skull X Room'),
- Pot(40, 6, PotItem.SmallMagic, 'Skull X Room'), Pot(24, 7, PotItem.SmallMagic, 'Skull X Room'), Pot(36, 7, PotItem.SmallMagic, 'Skull X Room'), Pot(12, 8, PotItem.Heart, 'Skull X Room'), Pot(48, 8, PotItem.Heart, 'Skull X Room'),
- Pot(24, 9, PotItem.SmallMagic, 'Skull X Room'), Pot(36, 9, PotItem.SmallMagic, 'Skull X Room'), Pot(20, 10, PotItem.FiveRupees, 'Skull X Room'), Pot(40, 10, PotItem.FiveRupees, 'Skull X Room'), Pot(12, 20, PotItem.Key, 'Skull 2 West Lobby'),
- Pot(48, 20, PotItem.Nothing, 'Skull 2 West Lobby')],
- 87: [Pot(92, 7, PotItem.BigMagic, 'Skull Lone Pot'), Pot(32, 4, PotItem.Nothing, 'Skull Big Key'), Pot(92, 23, PotItem.Bomb, 'Skull Pot Prison'), Pot(100, 23, PotItem.SmallMagic, 'Skull Pot Prison'),
- Pot(84, 25, PotItem.FiveRupees, 'Skull Pot Prison'), Pot(76, 27, PotItem.Heart, 'Skull Pot Prison'), Pot(12, 20, PotItem.SmallMagic, 'Skull 2 East Lobby'), Pot(48, 20, PotItem.SmallMagic, 'Skull 2 East Lobby'),
- Pot(30, 22, PotItem.Switch, 'Skull 2 East Lobby')],
- 88: [Pot(12, 7, PotItem.SmallMagic, 'Skull Pull Switch'), Pot(16, 7, PotItem.Nothing, 'Skull Pull Switch'), Pot(16, 8, PotItem.SmallMagic, 'Skull Pull Switch'), Pot(12, 12, PotItem.Nothing, 'Skull Pull Switch'),
- Pot(96, 9, PotItem.Nothing, 'Skull Pot Circle'), Pot(92, 8, PotItem.Nothing, 'Skull Pot Circle'), Pot(108, 8, PotItem.Nothing, 'Skull Pot Circle'), Pot(108, 6, PotItem.Nothing, 'Skull Pot Circle'),
- Pot(104, 5, PotItem.Nothing, 'Skull Pot Circle'), Pot(92, 6, PotItem.Nothing, 'Skull Pot Circle'), Pot(96, 5, PotItem.Bomb, 'Skull Pot Circle'), Pot(100, 5, PotItem.SmallMagic, 'Skull Pot Circle'),
- Pot(92, 7, PotItem.Heart, 'Skull Pot Circle'), Pot(108, 7, PotItem.Heart, 'Skull Pot Circle'), Pot(100, 9, PotItem.SmallMagic, 'Skull Pot Circle'), Pot(104, 9, PotItem.Bomb, 'Skull Pot Circle')],
- 89: [Pot(26, 43, PotItem.Heart, 'Skull 3 Lobby'), Pot(32, 40, PotItem.Nothing, 'Skull 3 Lobby'), Pot(76, 28, PotItem.Nothing, 'Skull East Bridge'), Pot(112, 28, PotItem.Nothing, 'Skull East Bridge')],
- 91: [Pot(218, 37, PotItem.Nothing, 'GT Hidden Spikes'), Pot(222, 37, PotItem.Switch, 'GT Hidden Spikes'), Pot(226, 37, PotItem.Nothing, 'GT Hidden Spikes')],
- 92: [Pot(228, 25, PotItem.Nothing, 'GT Refill'), Pot(104, 24, PotItem.Nothing, 'GT Refill'), Pot(228, 22, PotItem.Nothing, 'GT Refill'), Pot(216, 25, PotItem.Nothing, 'GT Refill'), Pot(84, 24, PotItem.Nothing, 'GT Refill'),
- Pot(216, 22, PotItem.Nothing, 'GT Refill'), Pot(94, 22, PotItem.Bomb, 'GT Refill'), Pot(94, 26, PotItem.BigMagic, 'GT Refill')],
- 93: [Pot(16, 5, PotItem.Bomb, 'GT Gauntlet 2'), Pot(44, 5, PotItem.FiveRupees, 'GT Gauntlet 2'), Pot(16, 11, PotItem.OneRupee, 'GT Gauntlet 2'), Pot(44, 11, PotItem.FiveArrows, 'GT Gauntlet 2'), Pot(12, 20, PotItem.FiveArrows, 'GT Gauntlet 3'),
- Pot(48, 20, PotItem.Bomb, 'GT Gauntlet 3'), Pot(12, 28, PotItem.SmallMagic, 'GT Gauntlet 3'), Pot(48, 28, PotItem.Bomb, 'GT Gauntlet 3')],
- 94: [Pot(92, 4, PotItem.SmallMagic, 'Ice Falling Square'), Pot(96, 4, PotItem.SmallMagic, 'Ice Falling Square'), Pot(76, 8, PotItem.Heart, 'Ice Falling Square'), Pot(112, 8, PotItem.Heart, 'Ice Falling Square')],
- 95: [Pot(44, 27, PotItem.Switch, 'Ice Spike Room')],
- 96: [Pot(76, 4, PotItem.Heart, 'Hyrule Castle West Lobby'), Pot(112, 4, PotItem.Heart, 'Hyrule Castle West Lobby')],
- 98: [Pot(208, 21, PotItem.Heart, 'Hyrule Castle East Lobby')],
- 99: [Pot(48, 4, PotItem.Nothing, 'Desert Tiles 1'), Pot(12, 4, PotItem.Nothing, 'Desert Tiles 1'), Pot(12, 8, PotItem.Nothing, 'Desert Tiles 1'), Pot(48, 12, PotItem.Nothing, 'Desert Tiles 1'), Pot(48, 8, PotItem.Heart, 'Desert Tiles 1'),
- Pot(12, 12, PotItem.Key, 'Desert Tiles 1')],
- 100: [Pot(12, 22, PotItem.Bomb, 'Thieves Attic Hint', PotFlags.SwitchLogicChange), Pot(16, 22, PotItem.Bomb, 'Thieves Attic Hint', PotFlags.SwitchLogicChange), Pot(20, 22, PotItem.Bomb, 'Thieves Attic Hint', PotFlags.SwitchLogicChange),
- Pot(36, 28, PotItem.Bomb, 'Thieves Attic'), Pot(40, 28, PotItem.SmallMagic, 'Thieves Attic'),
- Pot(44, 28, PotItem.SmallMagic, 'Thieves Attic'), Pot(48, 28, PotItem.Switch, 'Thieves Attic')],
- 101: [Pot(100, 28, PotItem.Bomb, 'Thieves Attic Window'), Pot(104, 28, PotItem.Bomb, 'Thieves Attic Window')],
- 102: [Pot(48, 37, PotItem.FiveArrows, 'Swamp Refill'), Pot(52, 37, PotItem.Bomb, 'Swamp Refill'), Pot(56, 37, PotItem.FiveRupees, 'Swamp Refill'), Pot(48, 38, PotItem.FiveArrows, 'Swamp Refill'), Pot(52, 38, PotItem.Bomb, 'Swamp Refill'),
- Pot(56, 38, PotItem.FiveRupees, 'Swamp Refill'), Pot(84, 5, PotItem.Heart, 'Swamp Behind Waterfall'), Pot(104, 5, PotItem.FiveArrows, 'Swamp Behind Waterfall'), Pot(84, 6, PotItem.Heart, 'Swamp Behind Waterfall'),
- Pot(104, 6, PotItem.Bomb, 'Swamp Behind Waterfall')],
- 103: [Pot(22, 26, PotItem.Nothing, 'Skull Left Drop'), Pot(18, 22, PotItem.Nothing, 'Skull Left Drop'), Pot(12, 7, PotItem.FiveArrows, 'Skull Left Drop'), Pot(48, 7, PotItem.SmallMagic, 'Skull Left Drop'),
- Pot(18, 23, PotItem.SmallMagic, 'Skull Left Drop'), Pot(18, 26, PotItem.Heart, 'Skull Left Drop'), Pot(96, 19, PotItem.Heart, 'Skull Compass Room'), Pot(74, 20, PotItem.SmallMagic, 'Skull Compass Room'),
- Pot(92, 9, PotItem.Nothing, 'Skull Compass Room'), Pot(84, 28, PotItem.Nothing, 'Skull Compass Room'), Pot(104, 28, PotItem.Heart, 'Skull Compass Room')],
- 104: [Pot(84, 14, PotItem.Nothing, 'Skull Pinball'), Pot(84, 13, PotItem.Nothing, 'Skull Pinball'), Pot(88, 12, PotItem.Nothing, 'Skull Pinball'), Pot(88, 6, PotItem.Nothing, 'Skull Pinball'), Pot(88, 5, PotItem.Nothing, 'Skull Pinball'),
- Pot(88, 4, PotItem.Nothing, 'Skull Pinball'), Pot(64, 17, PotItem.Nothing, 'Skull Pinball'), Pot(64, 15, PotItem.Nothing, 'Skull Pinball'), Pot(64, 7, PotItem.Heart, 'Skull Pinball'), Pot(88, 7, PotItem.SmallMagic, 'Skull Pinball'),
- Pot(64, 16, PotItem.Heart, 'Skull Pinball'), Pot(64, 24, PotItem.SmallMagic, 'Skull Pinball'), Pot(64, 25, PotItem.Heart, 'Skull Pinball')],
- 107: [Pot(28, 5, PotItem.Heart, 'GT Crystal Paths'), Pot(44, 5, PotItem.Nothing, 'GT Crystal Paths'), Pot(28, 8, PotItem.Nothing, 'GT Crystal Paths'), Pot(44, 8, PotItem.SmallMagic, 'GT Crystal Paths'),
- Pot(28, 11, PotItem.SmallMagic, 'GT Crystal Paths'), Pot(44, 11, PotItem.Nothing, 'GT Crystal Paths'), Pot(94, 25, PotItem.Nothing, 'GT Mimics 2'), Pot(98, 25, PotItem.FiveArrows, 'GT Mimics 2')],
- 108: [Pot(20, 6, PotItem.Heart, 'GT Quad Pot'), Pot(40, 6, PotItem.FiveArrows, 'GT Quad Pot'), Pot(20, 10, PotItem.Bomb, 'GT Quad Pot'), Pot(40, 10, PotItem.SmallMagic, 'GT Quad Pot')],
- 109: [Pot(28, 26, PotItem.Heart, 'GT Gauntlet 5'), Pot(32, 26, PotItem.Heart, 'GT Gauntlet 5'), Pot(28, 27, PotItem.SmallMagic, 'GT Gauntlet 5'), Pot(32, 27, PotItem.SmallMagic, 'GT Gauntlet 5')],
- 115: [Pot(154, 21, PotItem.FiveArrows, 'Desert Circle of Pots'), Pot(158, 21, PotItem.OneRupee, 'Desert Circle of Pots'), Pot(20, 23, PotItem.Switch, 'Desert Circle of Pots'), Pot(36, 23, PotItem.FiveRupees, 'Desert Circle of Pots'),
- Pot(144, 24, PotItem.Heart, 'Desert Circle of Pots'), Pot(168, 24, PotItem.FiveArrows, 'Desert Circle of Pots'), Pot(20, 26, PotItem.SmallMagic, 'Desert Circle of Pots'), Pot(36, 26, PotItem.Heart, 'Desert Circle of Pots'),
- Pot(154, 27, PotItem.OneRupee, 'Desert Circle of Pots'), Pot(158, 27, PotItem.FiveRupees, 'Desert Circle of Pots')],
- 116: [Pot(30, 5, PotItem.SmallMagic, 'Desert Map Room'), Pot(62, 5, PotItem.Switch, 'Desert Map Room'), Pot(94, 5, PotItem.SmallMagic, 'Desert Map Room'), Pot(14, 11, PotItem.Heart, 'Desert Map Room'),
- Pot(46, 11, PotItem.FiveArrows, 'Desert Map Room'), Pot(78, 11, PotItem.FiveArrows, 'Desert Map Room'), Pot(110, 11, PotItem.Heart, 'Desert Map Room')],
- 117: [Pot(148, 22, PotItem.SmallMagic, 'Desert Arrow Pot Corner'), Pot(160, 22, PotItem.FiveArrows, 'Desert Arrow Pot Corner'), Pot(172, 22, PotItem.Heart, 'Desert Arrow Pot Corner')],
- 118: [Pot(112, 12, PotItem.Heart, 'Swamp Drain Right'), Pot(84, 23, PotItem.Heart, 'Swamp Flooded Spot'), Pot(96, 23, PotItem.Heart, 'Swamp Flooded Spot')],
- 123: [Pot(48, 10, PotItem.Nothing, 'GT Conveyor Star Pits'), Pot(88, 10, PotItem.Nothing, 'GT Conveyor Star Pits'), Pot(76, 7, PotItem.Nothing, 'GT Conveyor Star Pits'), Pot(60, 4, PotItem.Heart, 'GT Conveyor Star Pits'),
- Pot(64, 4, PotItem.Key, 'GT Conveyor Star Pits')],
- 124: [Pot(36, 21, PotItem.Nothing, 'GT Falling Bridge'), Pot(24, 11, PotItem.Nothing, 'GT Falling Bridge'), Pot(28, 4, PotItem.Heart, 'GT Falling Bridge'), Pot(32, 4, PotItem.Heart, 'GT Falling Bridge')],
- 125: [Pot(44, 12, PotItem.Nothing, 'GT Firesnake Room'), Pot(44, 6, PotItem.Nothing, 'GT Firesnake Room'), Pot(112, 6, PotItem.Heart, 'GT Firesnake Room'), Pot(108, 20, PotItem.FiveArrows, 'GT Warp Maze - Pot Rail'),
- Pot(114, 20, PotItem.Bomb, 'GT Petting Zoo'), Pot(76, 28, PotItem.Bomb, 'GT Petting Zoo')],
- 126: [Pot(86, 15, PotItem.Heart, 'Ice Tall Hint'), Pot(82, 26, PotItem.SmallMagic, 'Ice Tall Hint'), Pot(100, 26, PotItem.Switch, 'Ice Tall Hint'), Pot(104, 26, PotItem.Nothing, 'Ice Tall Hint')],
- 128: [Pot(48, 4, PotItem.Heart, 'Hyrule Dungeon Cellblock'), Pot(52, 4, PotItem.Heart, 'Hyrule Dungeon Cellblock'), Pot(56, 4, PotItem.Heart, 'Hyrule Dungeon Cellblock')],
- 130: [Pot(50, 5, PotItem.Nothing, 'Hyrule Dungeon South Abyss'), Pot(50, 10, PotItem.Nothing, 'Hyrule Dungeon South Abyss'), Pot(76, 50, PotItem.Heart, 'Hyrule Dungeon South Abyss')],
- 131: [Pot(76, 4, PotItem.FiveArrows, 'Desert West Wing'), Pot(80, 4, PotItem.OneRupee, 'Desert West Wing'), Pot(76, 28, PotItem.FiveRupees, 'Desert West Wing'), Pot(80, 28, PotItem.FiveArrows, 'Desert West Wing')],
- 132: [Pot(64, 17, PotItem.Nothing, 'Desert Main Lobby'), Pot(60, 17, PotItem.Nothing, 'Desert Main Lobby'), Pot(80, 14, PotItem.Nothing, 'Desert Main Lobby'), Pot(44, 14, PotItem.Nothing, 'Desert Main Lobby'),
- Pot(100, 6, PotItem.Nothing, 'Desert Main Lobby'), Pot(24, 6, PotItem.Nothing, 'Desert Main Lobby'), Pot(24, 7, PotItem.FiveArrows, 'Desert Main Lobby'), Pot(100, 7, PotItem.FiveArrows, 'Desert Main Lobby')],
- 133: [Pot(44, 28, PotItem.Heart, 'Desert East Wing'), Pot(48, 28, PotItem.FiveArrows, 'Desert East Wing')],
- 135: [Pot(12, 11, PotItem.Nothing, 'Hera Tile Room'), Pot(16, 12, PotItem.Nothing, 'Hera Tile Room'), Pot(40, 12, PotItem.Nothing, 'Hera Tile Room'), Pot(32, 12, PotItem.Nothing, 'Hera Tile Room'), Pot(24, 12, PotItem.Nothing, 'Hera Tile Room'),
- Pot(16, 11, PotItem.Nothing, 'Hera Tile Room'), Pot(76, 20, PotItem.SmallMagic, 'Hera Torches'), Pot(112, 20, PotItem.BigMagic, 'Hera Torches')],
- 139: [Pot(76, 12, PotItem.Nothing, 'GT Conveyor Cross'), Pot(112, 12, PotItem.Key, 'GT Conveyor Cross'), Pot(32, 23, PotItem.Nothing, 'GT Hookshot South Platform'), Pot(28, 23, PotItem.Nothing, 'GT Hookshot South Platform'),
- Pot(32, 9, PotItem.SmallMagic, 'GT Hookshot East Platform'), Pot(76, 20, PotItem.Nothing, 'GT Map Room'), Pot(76, 28, PotItem.Heart, 'GT Map Room')],
- 140: [Pot(76, 12, PotItem.Switch, 'GT Hope Room'), Pot(112, 12, PotItem.SmallMagic, 'GT Hope Room'), Pot(76, 20, PotItem.Bomb, "GT Bob's Room"), Pot(92, 20, PotItem.Bomb, "GT Bob's Room"), Pot(100, 21, PotItem.FiveArrows, "GT Bob's Room"),
- Pot(104, 26, PotItem.Bomb, "GT Bob's Room"), Pot(88, 27, PotItem.Bomb, "GT Bob's Room")],
- 141: [Pot(204, 11, PotItem.Nothing, 'GT Speed Torch Upper'), Pot(204, 14, PotItem.BigMagic, 'GT Speed Torch Upper'), Pot(28, 23, PotItem.Heart, 'GT Pots n Blocks'), Pot(36, 23, PotItem.Heart, 'GT Pots n Blocks'),
- Pot(32, 24, PotItem.BigMagic, 'GT Pots n Blocks')],
- 142: [Pot(80, 5, PotItem.FiveArrows, 'Ice Lonely Freezor'), Pot(80, 6, PotItem.Nothing, 'Ice Lonely Freezor')],
- 145: [Pot(84, 4, PotItem.Heart, 'Mire Falling Foes'), Pot(104, 4, PotItem.SmallMagic, 'Mire Falling Foes')],
- 146: [Pot(86, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy'), Pot(92, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy'), Pot(98, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy'), Pot(104, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy')],
- 147: [Pot(28, 7, PotItem.Switch, 'Mire Dark Shooters'), Pot(96, 7, PotItem.Heart, 'Mire Dark Shooters', PotFlags.NoSwitch)],
- 150: [Pot(14, 18, PotItem.Nothing, 'GT Torch Cross'), Pot(32, 5, PotItem.Nothing, 'GT Torch Cross'), Pot(32, 17, PotItem.SmallMagic, 'GT Torch Cross'), Pot(32, 24, PotItem.SmallMagic, 'GT Torch Cross'),
- Pot(14, 24, PotItem.Nothing, 'GT Torch Cross'), Pot(76, 21, PotItem.Heart, 'GT Staredown'), Pot(112, 21, PotItem.BigMagic, 'GT Staredown')],
- 153: [Pot(40, 20, PotItem.SmallMagic, 'Eastern Darkness'), Pot(84, 20, PotItem.Heart, 'Eastern Darkness')],
- 155: [Pot(48, 4, PotItem.SmallMagic, 'GT Double Switch Pot Corners'), Pot(48, 12, PotItem.Key, 'GT Double Switch Pot Corners'), Pot(28, 24, PotItem.Nothing, 'GT Warp Maze - Mid Section'), Pot(32, 24, PotItem.Nothing, 'GT Warp Maze - Mid Section')],
- 156: [Pot(56, 8, PotItem.SmallMagic, 'GT Invisible Catwalk'), Pot(56, 9, PotItem.FiveArrows, 'GT Invisible Catwalk')],
- 157: [Pot(76, 4, PotItem.Bomb, 'GT Crystal Conveyor Left'), Pot(84, 4, PotItem.SmallMagic, 'GT Crystal Conveyor Left'), Pot(32, 7, PotItem.Nothing, 'GT Compass Room'), Pot(40, 9, PotItem.Nothing, 'GT Compass Room')],
- 159: [Pot(138, 20, PotItem.Nothing, 'Ice Many Pots'), Pot(138, 19, PotItem.Heart, 'Ice Many Pots'), Pot(178, 19, PotItem.Heart, 'Ice Many Pots'), Pot(40, 21, PotItem.Switch, 'Ice Many Pots'), Pot(138, 21, PotItem.Key, 'Ice Many Pots'),
- Pot(20, 27, PotItem.Heart, 'Ice Many Pots'), Pot(138, 27, PotItem.Heart, 'Ice Many Pots'), Pot(178, 28, PotItem.Heart, 'Ice Many Pots'), Pot(178, 21, PotItem.Nothing, 'Ice Many Pots'), Pot(178, 20, PotItem.Nothing, 'Ice Many Pots'),
- Pot(40, 27, PotItem.Nothing, 'Ice Many Pots'), Pot(178, 27, PotItem.Nothing, 'Ice Many Pots'), Pot(178, 26, PotItem.Nothing, 'Ice Many Pots'), Pot(138, 28, PotItem.Nothing, 'Ice Many Pots'), Pot(138, 26, PotItem.Nothing, 'Ice Many Pots'),
- Pot(20, 21, PotItem.Nothing, 'Ice Many Pots')],
- 161: [Pot(150, 6, PotItem.Key, 'Mire Fishbone'), Pot(100, 11, PotItem.SmallMagic, 'Mire Fishbone'), Pot(104, 12, PotItem.Heart, 'Mire Fishbone'), Pot(108, 13, PotItem.SmallMagic, 'Mire Fishbone'), Pot(112, 14, PotItem.Heart, 'Mire Fishbone'),
- Pot(96, 27, PotItem.Nothing, 'Mire South Fish'), Pot(92, 21, PotItem.Nothing, 'Mire South Fish'), Pot(96, 23, PotItem.Heart, 'Mire South Fish'), Pot(92, 25, PotItem.Nothing, 'Mire South Fish'),
- Pot(76, 28, PotItem.Nothing, 'Mire South Fish'), Pot(112, 28, PotItem.Nothing, 'Mire South Fish')],
- 162: [Pot(12, 28, PotItem.BigMagic, 'Mire Left Bridge')],
- 168: [Pot(138, 28, PotItem.Nothing, 'Eastern Stalfos Spawn'), Pot(178, 28, PotItem.Nothing, 'Eastern Stalfos Spawn'), Pot(178, 19, PotItem.Nothing, 'Eastern Stalfos Spawn'), Pot(138, 19, PotItem.Heart, 'Eastern Stalfos Spawn'),
- Pot(30, 24, PotItem.OneRupee, 'Eastern Stalfos Spawn')],
- 169: [Pot(144, 43, PotItem.FiveArrows, 'Eastern Courtyard'), Pot(236, 43, PotItem.FiveArrows, 'Eastern Courtyard'), Pot(144, 44, PotItem.FiveArrows, 'Eastern Courtyard'), Pot(236, 44, PotItem.Heart, 'Eastern Courtyard'),
- Pot(12, 19, PotItem.Nothing, 'Eastern Courtyard Ledge'), Pot(112, 19, PotItem.Nothing, 'Eastern Courtyard Ledge'), Pot(16, 20, PotItem.Heart, 'Eastern Courtyard Ledge'), Pot(108, 20, PotItem.Heart, 'Eastern Courtyard Ledge')],
- 170: [Pot(212, 10, PotItem.Nothing, 'Eastern Pot Switch'), Pot(232, 10, PotItem.Nothing, 'Eastern Pot Switch'), Pot(232, 5, PotItem.Nothing, 'Eastern Pot Switch'), Pot(212, 5, PotItem.Heart, 'Eastern Pot Switch'),
- Pot(94, 8, PotItem.Switch, 'Eastern Pot Switch'), Pot(108, 55, PotItem.Heart, 'Eastern Map Balcony'), Pot(108, 56, PotItem.Heart, 'Eastern Map Balcony'), Pot(108, 57, PotItem.Heart, 'Eastern Map Balcony')],
- 171: [Pot(20, 24, PotItem.Key, 'Thieves Spike Switch')],
- 174: [Pot(76, 12, PotItem.Switch, 'Iced T')],
- 176: [Pot(20, 27, PotItem.Nothing, 'Tower Circle of Pots'), Pot(24, 24, PotItem.Nothing, 'Tower Circle of Pots'), Pot(44, 25, PotItem.Nothing, 'Tower Circle of Pots'), Pot(20, 21, PotItem.Bomb, 'Tower Circle of Pots'),
- Pot(28, 21, PotItem.OneRupee, 'Tower Circle of Pots'), Pot(32, 21, PotItem.FiveRupees, 'Tower Circle of Pots'), Pot(40, 21, PotItem.FiveArrows, 'Tower Circle of Pots'), Pot(16, 23, PotItem.FiveRupees, 'Tower Circle of Pots'),
- Pot(44, 23, PotItem.OneRupee, 'Tower Circle of Pots'), Pot(36, 24, PotItem.Heart, 'Tower Circle of Pots'), Pot(16, 25, PotItem.Heart, 'Tower Circle of Pots'), Pot(28, 27, PotItem.FiveArrows, 'Tower Circle of Pots'),
- Pot(40, 27, PotItem.Bomb, 'Tower Circle of Pots'), Pot(32, 27, PotItem.Nothing, 'Tower Circle of Pots')],
- 177: [Pot(76, 4, PotItem.Heart, 'Mire Spike Barrier'), Pot(112, 4, PotItem.OneRupee, 'Mire Spike Barrier')],
- 178: [Pot(48, 40, PotItem.OneRupee, 'Mire BK Door Room'), Pot(76, 40, PotItem.OneRupee, 'Mire BK Door Room'), Pot(48, 41, PotItem.Nothing, 'Mire BK Door Room'), Pot(76, 41, PotItem.Heart, 'Mire BK Door Room'),
- Pot(48, 42, PotItem.Nothing, 'Mire BK Door Room'), Pot(76, 40, PotItem.Nothing, 'Mire BK Door Room')],
- 179: [Pot(12, 20, PotItem.Key, 'Mire Spikes'), Pot(48, 20, PotItem.SmallMagic, 'Mire Spikes'), Pot(48, 28, PotItem.Switch, 'Mire Spikes')],
- 180: [Pot(44, 28, PotItem.BigMagic, 'TR Final Abyss'), Pot(48, 28, PotItem.Heart, 'TR Final Abyss')],
- 181: [Pot(112, 4, PotItem.FiveRupees, 'TR Dark Ride'), Pot(112, 15, PotItem.Heart, 'TR Dark Ride'), Pot(76, 16, PotItem.Switch, 'TR Dark Ride'), Pot(112, 16, PotItem.BigMagic, 'TR Dark Ride'), Pot(112, 17, PotItem.Heart, 'TR Dark Ride'),
- Pot(112, 28, PotItem.Bomb, 'TR Dark Ride')],
- 182: [Pot(94, 9, PotItem.BigMagic, 'TR Refill')],
- 183: [Pot(30, 5, PotItem.SmallMagic, 'TR Roller Room')],
- 184: [Pot(96, 13, PotItem.Switch, 'Eastern Big Key'), Pot(88, 16, PotItem.Heart, 'Eastern Big Key'), Pot(104, 16, PotItem.Heart, 'Eastern Big Key')],
- 185: [Pot(92, 18, PotItem.OneRupee, 'Eastern Cannonball'), Pot(96, 18, PotItem.FiveRupees, 'Eastern Cannonball'), Pot(104, 18, PotItem.FiveRupees, 'Eastern Cannonball'), Pot(108, 18, PotItem.OneRupee, 'Eastern Cannonball')],
- 186: [Pot(100, 8, PotItem.Nothing, 'Eastern Dark Pots'), Pot(88, 8, PotItem.Nothing, 'Eastern Dark Pots'), Pot(94, 4, PotItem.OneRupee, 'Eastern Dark Pots'), Pot(76, 6, PotItem.Heart, 'Eastern Dark Pots'),
- Pot(112, 6, PotItem.Key, 'Eastern Dark Pots'), Pot(76, 10, PotItem.Heart, 'Eastern Dark Pots'), Pot(112, 10, PotItem.SmallMagic, 'Eastern Dark Pots'), Pot(94, 12, PotItem.OneRupee, 'Eastern Dark Pots')],
- 188: [Pot(86, 4, PotItem.Heart, 'Thieves Hallway'), Pot(102, 4, PotItem.Key, 'Thieves Hallway'), Pot(138, 3, PotItem.Bomb, 'Thieves Conveyor Maze'), Pot(178, 3, PotItem.Switch, 'Thieves Conveyor Maze'),
- Pot(138, 12, PotItem.Heart, 'Thieves Conveyor Maze'), Pot(178, 12, PotItem.Bomb, 'Thieves Conveyor Maze'), Pot(12, 20, PotItem.Nothing, 'Thieves Pot Alcove Mid'), Pot(48, 20, PotItem.Bomb, 'Thieves Pot Alcove Mid'),
- Pot(12, 28, PotItem.Bomb, 'Thieves Pot Alcove Mid'), Pot(48, 28, PotItem.Bomb, 'Thieves Pot Alcove Mid'), Pot(28, 21, PotItem.FiveRupees, 'Thieves Pot Alcove Top'), Pot(32, 21, PotItem.FiveRupees, 'Thieves Pot Alcove Top'),
- Pot(28, 27, PotItem.FiveRupees, 'Thieves Pot Alcove Bottom'), Pot(32, 27, PotItem.FiveRupees, 'Thieves Pot Alcove Bottom')],
- 190: [Pot(92, 25, PotItem.Switch, 'Ice Switch Room')],
- 191: [Pot(40, 20, PotItem.FiveArrows, 'Ice Refill'), Pot(44, 20, PotItem.Heart, 'Ice Refill'), Pot(48, 20, PotItem.Bomb, 'Ice Refill'), Pot(40, 28, PotItem.SmallMagic, 'Ice Refill'), Pot(44, 28, PotItem.SmallMagic, 'Ice Refill'),
- Pot(48, 28, PotItem.SmallMagic, 'Ice Refill')],
- 192: [Pot(48, 10, PotItem.Bomb, 'Tower Dark Pits'), Pot(12, 14, PotItem.FiveRupees, 'Tower Dark Pits'), Pot(12, 26, PotItem.Heart, 'Tower Dark Pits'), Pot(28, 27, PotItem.OneRupee, 'Tower Dark Pits')],
- 194: [Pot(180, 7, PotItem.Switch, 'Mire Hub Switch'), Pot(100, 46, PotItem.SmallMagic, 'Mire Hub Right'), Pot(68, 48, PotItem.OneRupee, 'Mire Hub'), Pot(64, 52, PotItem.FiveArrows, 'Mire Hub')],
- 196: [Pot(84, 9, PotItem.Bomb, 'TR Crystal Maze Interior'), Pot(24, 14, PotItem.Heart, 'TR Crystal Maze Interior'), Pot(56, 17, PotItem.FiveRupees, 'TR Crystal Maze Interior'), Pot(84, 17, PotItem.Bomb, 'TR Crystal Maze Interior'),
- Pot(12, 21, PotItem.FiveArrows, 'TR Crystal Maze Interior'), Pot(76, 23, PotItem.OneRupee, 'TR Crystal Maze Interior'), Pot(48, 25, PotItem.SmallMagic, 'TR Crystal Maze Interior'), Pot(12, 26, PotItem.Heart, 'TR Crystal Maze Interior')],
- 198: [Pot(12, 7, PotItem.BigMagic, 'TR Hub'), Pot(12, 25, PotItem.Heart, 'TR Hub')],
- 199: [Pot(12, 10, PotItem.Heart, 'TR Torches'), Pot(12, 11, PotItem.BigMagic, 'TR Torches'), Pot(12, 22, PotItem.SmallMagic, 'TR Torches Ledge'), Pot(12, 28, PotItem.FiveArrows, 'TR Torches Ledge')],
- 201: [Pot(30, 22, PotItem.OneRupee, 'Eastern Lobby'), Pot(94, 22, PotItem.OneRupee, 'Eastern Lobby'), Pot(60, 22, PotItem.Switch, 'Eastern Lobby')],
- 203: [Pot(80, 4, PotItem.Nothing, 'Thieves Ambush'), Pot(80, 28, PotItem.Nothing, 'Thieves Ambush'), Pot(88, 16, PotItem.Heart, 'Thieves Ambush'), Pot(88, 28, PotItem.FiveRupees, 'Thieves Ambush')],
- 204: [Pot(36, 4, PotItem.FiveRupees, 'Thieves Rail Ledge'), Pot(36, 28, PotItem.FiveRupees, 'Thieves Rail Ledge'), Pot(112, 4, PotItem.Heart, 'Thieves BK Corner'), Pot(112, 28, PotItem.Bomb, 'Thieves BK Corner')],
- 206: [Pot(76, 8, PotItem.SmallMagic, 'Ice Antechamber'), Pot(80, 8, PotItem.SmallMagic, 'Ice Antechamber'), Pot(108, 12, PotItem.Bomb, 'Ice Antechamber'), Pot(112, 12, PotItem.FiveArrows, 'Ice Antechamber'),
- Pot(204, 11, PotItem.Hole, 'Ice Antechamber')],
- 208: [Pot(158, 5, PotItem.SmallMagic, 'Tower Dark Maze'), Pot(140, 11, PotItem.OneRupee, 'Tower Dark Maze'), Pot(42, 13, PotItem.SmallMagic, 'Tower Dark Maze'), Pot(48, 16, PotItem.Heart, 'Tower Dark Maze'),
- Pot(176, 20, PotItem.OneRupee, 'Tower Dark Maze'), Pot(146, 23, PotItem.FiveRupees, 'Tower Dark Maze'), Pot(12, 28, PotItem.Heart, 'Tower Dark Maze')],
- 209: [Pot(48, 4, PotItem.BigMagic, 'Mire Conveyor Barrier'), Pot(168, 7, PotItem.OneRupee, 'Mire Conveyor Barrier'), Pot(76, 4, PotItem.OneRupee, 'Mire Neglected Room'), Pot(112, 4, PotItem.FiveArrows, 'Mire Neglected Room'),
- Pot(76, 12, PotItem.Nothing, 'Mire Neglected Room'), Pot(112, 12, PotItem.OneRupee, 'Mire Neglected Room')],
- 214: [Pot(92, 22, PotItem.BigMagic, 'TR Main Lobby'), Pot(96, 22, PotItem.Bomb, 'TR Main Lobby')],
- 216: [Pot(202, 8, PotItem.Heart, 'Eastern Duo Eyegores'), Pot(242, 8, PotItem.FiveArrows, 'Eastern Duo Eyegores'), Pot(202, 10, PotItem.FiveArrows, 'Eastern Duo Eyegores'), Pot(242, 10, PotItem.FiveArrows, 'Eastern Duo Eyegores'),
- Pot(202, 12, PotItem.FiveArrows, 'Eastern Duo Eyegores'), Pot(242, 12, PotItem.Heart, 'Eastern Duo Eyegores'), Pot(92, 24, PotItem.Heart, 'Eastern Single Eyegore'), Pot(96, 24, PotItem.FiveArrows, 'Eastern Single Eyegore')],
- 217: [Pot(92, 20, PotItem.FiveArrows, 'Eastern False Switches'), Pot(92, 28, PotItem.Heart, 'Eastern False Switches')],
- 218: [Pot(24, 23, PotItem.FiveArrows, 'Eastern Attic Start'), Pot(36, 23, PotItem.FiveArrows, 'Eastern Attic Start'), Pot(24, 25, PotItem.Switch, 'Eastern Attic Start'), Pot(36, 25, PotItem.Heart, 'Eastern Attic Start')],
- 219: [Pot(12, 4, PotItem.Nothing, 'Thieves Lobby'), Pot(62, 19, PotItem.Nothing, 'Thieves Lobby'), Pot(112, 4, PotItem.FiveRupees, 'Thieves Lobby'), Pot(88, 16, PotItem.Heart, 'Thieves Lobby')],
- 220: [Pot(56, 4, PotItem.FiveRupees, 'Thieves Compass Room'), Pot(112, 4, PotItem.Bomb, 'Thieves Compass Room'), Pot(68, 16, PotItem.Heart, 'Thieves Compass Room'), Pot(12, 28, PotItem.FiveArrows, 'Thieves Compass Room')],
- 227: [Pot(88, 55, PotItem.Nothing, 'Bat Cave (right)'), Pot(100, 57, PotItem.OneRupee, 'Bat Cave (right)')],
- 228: [Pot(32, 9, PotItem.FiveRupees, 'Old Man House'), Pot(112, 10, PotItem.OneRupee, 'Old Man House')],
- 229: [Pot(48, 4, PotItem.OneRupee, 'Old Man House Back'), Pot(76, 4, PotItem.OneRupee, 'Old Man House Back'), Pot(112, 16, PotItem.OneRupee, 'Old Man House Back'), Pot(64, 18, PotItem.FiveRupees, 'Old Man House Back')],
- 230: [Pot(108, 12, PotItem.FiveArrows, 'Death Mountain Return Cave'), Pot(88, 16, PotItem.Heart, 'Death Mountain Return Cave'), Pot(72, 20, PotItem.Nothing, 'Death Mountain Return Cave'),
- Pot(56, 24, PotItem.OneRupee, 'Death Mountain Return Cave')],
- 231: [Pot(68, 5, PotItem.OneRupee, 'Death Mountain Return Cave'), Pot(72, 5, PotItem.OneRupee, 'Death Mountain Return Cave')],
- 232: [Pot(96, 4, PotItem.Heart, 'Superbunny Cave')],
- 235: [Pot(206, 8, PotItem.FiveRupees, 'Bumper Cave'), Pot(210, 8, PotItem.FiveRupees, 'Bumper Cave'), Pot(88, 14, PotItem.SmallMagic, 'Bumper Cave'), Pot(92, 14, PotItem.Heart, 'Bumper Cave'), Pot(96, 14, PotItem.SmallMagic, 'Bumper Cave')],
- 241: [Pot(64, 5, PotItem.Heart, 'Old Man Cave')],
- 248: [Pot(242, 13, PotItem.BigMagic, 'Superbunny Cave')],
- 253: [Pot(88, 6, PotItem.FiveRupees, 'Fairy Ascension Cave (Top)'), Pot(100, 6, PotItem.FiveRupees, 'Fairy Ascension Cave (Top)'), Pot(84, 23, PotItem.FiveRupees, 'Fairy Ascension Cave (Bottom)'),
- Pot(84, 24, PotItem.FiveRupees, 'Fairy Ascension Cave (Bottom)')],
- 255: [Pot(92, 8, PotItem.Heart, 'Paradox Cave'), Pot(96, 8, PotItem.Heart, 'Paradox Cave'), Pot(112, 28, PotItem.OneRupee, 'Paradox Cave Front')],
- 257: [Pot(12, 20, PotItem.Heart, 'Snitch Lady (East)'), Pot(224, 19, PotItem.Chicken, 'Snitch Lady (West)'), Pot(228, 19, PotItem.Heart, 'Snitch Lady (West)')],
- 258: [Pot(146, 19, PotItem.Heart, 'Sick Kids House'), Pot(150, 19, PotItem.Heart, 'Sick Kids House')],
- 259: [Pot(140, 7, PotItem.Chicken, 'Tavern'), Pot(140, 9, PotItem.Nothing, 'Tavern'), Pot(12, 12, PotItem.Heart, 'Tavern (Front)')],
- 260: [Pot(202, 21, PotItem.Heart, 'Links House'), Pot(202, 22, PotItem.Heart, 'Links House'), Pot(202, 23, PotItem.Heart, 'Links House')],
- 261: [Pot(30, 20, PotItem.Heart, 'Sahasrahlas Hut'), Pot(28, 21, PotItem.Heart, 'Sahasrahlas Hut'), Pot(32, 21, PotItem.Heart, 'Sahasrahlas Hut')],
- 263: [Pot(214, 23, PotItem.Bomb, 'Light World Bomb Hut'), Pot(222, 23, PotItem.FiveArrows, 'Light World Bomb Hut'), Pot(230, 23, PotItem.Bomb, 'Light World Bomb Hut'), Pot(214, 25, PotItem.OneRupee, 'Light World Bomb Hut'),
- Pot(222, 25, PotItem.Nothing, 'Light World Bomb Hut'), Pot(230, 25, PotItem.OneRupee, 'Light World Bomb Hut'), Pot(214, 27, PotItem.Bomb, 'Light World Bomb Hut'), Pot(230, 27, PotItem.Bomb, 'Light World Bomb Hut')],
- 264: [Pot(166, 19, PotItem.Chicken, 'Chicken House')],
- 268: [Pot(88, 14, PotItem.Heart, 'Hookshot Fairy')],
- 276: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint'), Pot(96, 4, PotItem.Heart, 'Dark Desert Hint'), Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint'), Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint'), Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint'),
- Pot(96, 10, PotItem.Heart, 'Dark Desert Hint')],
- 279: [Pot(138, 3, PotItem.Heart, 'Spike Cave'), Pot(142, 3, PotItem.Heart, 'Spike Cave'), Pot(166, 3, PotItem.Heart, 'Spike Cave'), Pot(170, 3, PotItem.Heart, 'Spike Cave'), Pot(138, 4, PotItem.Heart, 'Spike Cave'),
- Pot(142, 4, PotItem.Heart, 'Spike Cave'), Pot(166, 4, PotItem.Heart, 'Spike Cave'), Pot(170, 4, PotItem.Heart, 'Spike Cave')],
- 281: [Pot(44, 28, PotItem.Heart, 'Blinds Hideout'), Pot(48, 28, PotItem.OneRupee, 'Blinds Hideout'), Pot(76, 28, PotItem.Heart, 'Blinds Hideout'), Pot(80, 28, PotItem.Heart, 'Blinds Hideout')],
- 282: [Pot(214, 10, PotItem.Heart, 'Palace of Darkness Hint'), Pot(218, 10, PotItem.Heart, 'Palace of Darkness Hint'), Pot(226, 10, PotItem.Heart, 'Palace of Darkness Hint'), Pot(230, 10, PotItem.Heart, 'Palace of Darkness Hint')],
- 283: [Pot(24, 53, PotItem.Nothing, 'Cave 45'), Pot(24, 54, PotItem.Heart, 'Cave 45'), Pot(32, 54, PotItem.Heart, 'Cave 45'), Pot(40, 54, PotItem.Heart, 'Cave 45'), Pot(24, 55, PotItem.Heart, 'Cave 45'), Pot(28, 56, PotItem.Heart, 'Cave 45'),
- Pot(92, 22, PotItem.Bomb, 'Graveyard Cave'), Pot(96, 22, PotItem.Heart, 'Graveyard Cave'), Pot(92, 23, PotItem.Bomb, 'Graveyard Cave'), Pot(96, 23, PotItem.Heart, 'Graveyard Cave'), Pot(92, 24, PotItem.Bomb, 'Graveyard Cave'),
- Pot(96, 24, PotItem.Heart, 'Graveyard Cave'), Pot(92, 25, PotItem.Bomb, 'Graveyard Cave'), Pot(96, 25, PotItem.Heart, 'Graveyard Cave')],
- 285: [Pot(60, 6, PotItem.FiveRupees, 'Blinds Hideout'), Pot(64, 6, PotItem.FiveRupees, 'Blinds Hideout'), Pot(60, 7, PotItem.FiveRupees, 'Blinds Hideout'), Pot(64, 7, PotItem.FiveRupees, 'Blinds Hideout'),
- Pot(60, 8, PotItem.FiveRupees, 'Blinds Hideout'), Pot(64, 8, PotItem.FiveRupees, 'Blinds Hideout')],
- 287: [Pot(174, 28, PotItem.Heart, 'Lumberjack House'), Pot(178, 28, PotItem.Heart, 'Lumberjack House')],
- 292: [Pot(20, 20, PotItem.FiveRupees, '50 Rupee Cave'), Pot(40, 20, PotItem.FiveRupees, '50 Rupee Cave'), Pot(20, 21, PotItem.FiveRupees, '50 Rupee Cave'), Pot(40, 21, PotItem.FiveRupees, '50 Rupee Cave'),
- Pot(20, 22, PotItem.FiveRupees, '50 Rupee Cave'), Pot(40, 22, PotItem.FiveRupees, '50 Rupee Cave'), Pot(24, 24, PotItem.FiveRupees, '50 Rupee Cave'), Pot(28, 24, PotItem.FiveRupees, '50 Rupee Cave'),
- Pot(32, 24, PotItem.FiveRupees, '50 Rupee Cave'), Pot(36, 24, PotItem.FiveRupees, '50 Rupee Cave')],
- 293: [Pot(24, 25, PotItem.FiveRupees, '20 Rupee Cave'), Pot(28, 25, PotItem.FiveRupees, '20 Rupee Cave'), Pot(32, 25, PotItem.FiveRupees, '20 Rupee Cave'), Pot(36, 25, PotItem.FiveRupees, '20 Rupee Cave'),
- Pot(88, 22, PotItem.Heart, 'Dev Cave Hint'), Pot(100, 22, PotItem.Heart, 'Dev Cave Hint'), Pot(88, 28, PotItem.Heart, 'Dev Cave Hint'), Pot(100, 28, PotItem.Heart, 'Dev Cave Hint')],
- 295: [Pot(24, 25, PotItem.Nothing, 'Hammer Pegs Cave'), Pot(28, 25, PotItem.Nothing, 'Hammer Pegs Cave'), Pot(32, 25, PotItem.Nothing, 'Hammer Pegs Cave'), Pot(36, 25, PotItem.Nothing, 'Hammer Pegs Cave')],
+ 2: [Pot(80, 6, PotItem.Nothing, 'Sewers Yet More Rats', PotFlags.LowerRegion, obj=RoomObject(0x0A8B57, [0xA3, 0x33, 0xFA])),
+ Pot(80, 8, PotItem.Nothing, 'Sewers Yet More Rats', PotFlags.LowerRegion, obj=RoomObject(0x0A8B5A, [0xA3, 0x43, 0xFA])),
+ Pot(44, 8, PotItem.Nothing, 'Sewers Yet More Rats', PotFlags.LowerRegion, obj=RoomObject(0x0A8B5D, [0x5B, 0x43, 0xFA])),
+ Pot(44, 10, PotItem.Nothing, 'Sewers Yet More Rats', PotFlags.LowerRegion, obj=RoomObject(0x0A8B60, [0x5B, 0x53, 0xFA]))],
+ 4: [Pot(162, 25, PotItem.Nothing, 'TR Dash Room', obj=RoomObject(0x1FE220, [0x47, 0xCF, 0xFA])),
+ Pot(152, 25, PotItem.Nothing, 'TR Dash Room', obj=RoomObject(0x1FE21D, [0x33, 0xCF, 0xFA])),
+ Pot(152, 22, PotItem.Nothing, 'TR Dash Room', obj=RoomObject(0x1FE217, [0x33, 0xB7, 0xFA])),
+ Pot(162, 22, PotItem.Nothing, 'TR Dash Room', obj=RoomObject(0x1FE21A, [0x47, 0xB7, 0xFA])),
+ Pot(204, 19, PotItem.Bomb, 'TR Tongue Pull', obj=RoomObject(0x1FE241, [0x9B, 0x9F, 0xFA])),
+ Pot(240, 19, PotItem.Bomb, 'TR Tongue Pull', obj=RoomObject(0x1FE244, [0xE3, 0x9F, 0xFA]))],
+ 9: [Pot(12, 4, PotItem.OneRupee, 'PoD Shooter Room', obj=RoomObject(0x1FACEC, [0x1B, 0x23, 0xFA])),
+ Pot(48, 4, PotItem.Heart, 'PoD Shooter Room', obj=RoomObject(0x1FACEF, [0x63, 0x23, 0xFA])),
+ Pot(12, 12, PotItem.Switch, 'PoD Shooter Room', obj=RoomObject(0x1FACF5, [0x1B, 0x63, 0xFA]))],
+ 0xa: [Pot(96, 8, PotItem.Heart, 'PoD Stalfos Basement', obj=RoomObject(0x1FA6EC, [0xC3, 0x43, 0xFA])),
+ Pot(104, 8, PotItem.Heart, 'PoD Stalfos Basement', obj=RoomObject(0x1FA6F2, [0xD3, 0x43, 0xFA])),
+ Pot(204, 11, PotItem.Switch, 'PoD Stalfos Basement', obj=RoomObject(0x1FA6FB, [0x9B, 0x5F, 0xFA])),
+ Pot(100, 9, PotItem.Nothing, 'PoD Stalfos Basement', obj=RoomObject(0x1FA6EF, [0xCB, 0x4B, 0xFA])),
+ Pot(100, 7, PotItem.Nothing, 'PoD Stalfos Basement', obj=RoomObject(0x1FA6F5, [0xCB, 0x3B, 0xFA])),
+ Pot(156, 17, PotItem.Bomb, 'PoD Basement Ledge', PotFlags.SwitchLogicChange, obj=RoomObject(0x1FA6E6, [0x3B, 0x8F, 0xFA])),
+ Pot(160, 17, PotItem.FiveArrows, 'PoD Basement Ledge', PotFlags.SwitchLogicChange, obj=RoomObject(0x1FA6E9, [0x43, 0x8F, 0xFA]))],
+ 0xb: [Pot(202, 3, PotItem.Bomb, 'PoD Dark Pegs Left', obj=RoomObject(0x1FAB48, [0x97, 0x1F, 0xFA])),
+ Pot(202, 12, PotItem.Bomb, 'PoD Dark Pegs Left', obj=RoomObject(0x1FAB4E, [0x97, 0x67, 0xFA]))],
+ 0x11: [Pot(152, 19, PotItem.Nothing, 'Sewers Secret Room', obj=RoomObject(0x0A8C07, [0x33, 0x9F, 0xFA])),
+ Pot(152, 15, PotItem.Nothing, 'Sewers Secret Room', obj=RoomObject(0x0A8BF5, [0x33, 0x7F, 0xFA])),
+ Pot(144, 15, PotItem.Heart, 'Sewers Secret Room', obj=RoomObject(0x0A8BF2, [0x23, 0x7F, 0xFA])),
+ Pot(160, 15, PotItem.Heart, 'Sewers Secret Room', obj=RoomObject(0x0A8BF8, [0x43, 0x7F, 0xFA])),
+ Pot(144, 19, PotItem.Heart, 'Sewers Secret Room', obj=RoomObject(0x0A8C04, [0x23, 0x9F, 0xFA])),
+ Pot(160, 19, PotItem.Heart, 'Sewers Secret Room', obj=RoomObject(0x0A8C0A, [0x43, 0x9F, 0xFA]))],
+ 0x15: [Pot(96, 4, PotItem.Bomb, 'TR Pipe Pit', obj=RoomObject(0x1FE551, [0xC3, 0x23, 0xFA])),
+ Pot(100, 4, PotItem.SmallMagic, 'TR Pipe Pit', obj=RoomObject(0x1FE554, [0xCB, 0x23, 0xFA])),
+ Pot(104, 4, PotItem.Heart, 'TR Pipe Pit', obj=RoomObject(0x1FE557, [0xD3, 0x23, 0xFA])),
+ Pot(108, 4, PotItem.SmallMagic, 'TR Pipe Pit', obj=RoomObject(0x1FE55A, [0xDB, 0x23, 0xFA])),
+ Pot(112, 4, PotItem.FiveArrows, 'TR Pipe Pit', obj=RoomObject(0x1FE55D, [0xE3, 0x23, 0xFA])),
+ Pot(12, 6, PotItem.OneRupee, 'TR Pipe Pit', obj=RoomObject(0x1FE548, [0x1B, 0x33, 0xFA])),
+ Pot(16, 6, PotItem.FiveArrows, 'TR Pipe Pit', obj=RoomObject(0x1FE54B, [0x23, 0x33, 0xFA])),
+ Pot(20, 6, PotItem.FiveRupees, 'TR Pipe Pit', obj=RoomObject(0x1FE54E, [0x2B, 0x33, 0xFA])),
+ Pot(70, 11, PotItem.BigMagic, 'TR Pipe Ledge', obj=RoomObject(0x1FE545, [0x8F, 0x5B, 0xFA]))],
+ 0x16: [Pot(188, 3, PotItem.Heart, 'Swamp I', obj=RoomObject(0x1FA09C, [0x7B, 0x1F, 0xFA])),
+ Pot(192, 3, PotItem.Heart, 'Swamp I', obj=RoomObject(0x1FA09F, [0x83, 0x1F, 0xFA])),
+ Pot(188, 4, PotItem.SmallMagic, 'Swamp I', obj=RoomObject(0x1FA0A2, [0x7B, 0x27, 0xFA])),
+ Pot(192, 4, PotItem.SmallMagic, 'Swamp I', obj=RoomObject(0x1FA0A5, [0x83, 0x27, 0xFA])),
+ Pot(188, 5, PotItem.FiveArrows, 'Swamp I', obj=RoomObject(0x1FA0A8, [0x7B, 0x2F, 0xFA])),
+ Pot(192, 5, PotItem.FiveArrows, 'Swamp I', obj=RoomObject(0x1FA0AB, [0x83, 0x2F, 0xFA])),
+ Pot(188, 6, PotItem.Bomb, 'Swamp I', obj=RoomObject(0x1FA0AE, [0x7B, 0x37, 0xFA])),
+ Pot(192, 6, PotItem.Bomb, 'Swamp I', obj=RoomObject(0x1FA0B1, [0x83, 0x37, 0xFA])),
+ Pot(240, 19, PotItem.Key, 'Swamp Waterway', obj=RoomObject(0x1FA0D8, [0xE3, 0x9F, 0xFA]))],
+ 0x17: [Pot(100, 13, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCE0, [0xCB, 0x6B, 0xFA])),
+ Pot(100, 14, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCE3, [0xCB, 0x73, 0xFA])),
+ Pot(100, 15, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCE6, [0xCB, 0x7B, 0xFA])),
+ Pot(100, 16, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCE9, [0xCB, 0x83, 0xFA])),
+ Pot(100, 17, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCEC, [0xCB, 0x8B, 0xFA])),
+ Pot(100, 18, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCEF, [0xCB, 0x93, 0xFA])),
+ Pot(104, 13, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCF2, [0xD3, 0x6B, 0xFA])),
+ Pot(104, 14, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCF5, [0xD3, 0x73, 0xFA])),
+ Pot(104, 15, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCF8, [0xD3, 0x7B, 0xFA])),
+ Pot(104, 16, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCFB, [0xD3, 0x83, 0xFA])),
+ Pot(104, 17, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCFE, [0xD3, 0x8B, 0xFA])),
+ Pot(104, 18, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCD01, [0xD3, 0x93, 0xFA]))],
+ 0x1A: [Pot(28, 5, PotItem.Bomb, 'PoD Falling Bridge Ledge', obj=RoomObject(0x1FA60D, [0x3B, 0x2B, 0xFA])),
+ Pot(32, 5, PotItem.Bomb, 'PoD Falling Bridge Ledge', obj=RoomObject(0x1FA610, [0x43, 0x2B, 0xFA])),
+ Pot(28, 27, PotItem.Bomb, 'PoD Falling Bridge', obj=RoomObject(0x1FA5F5, [0x3B, 0xDB, 0xFA])),
+ Pot(32, 27, PotItem.Bomb, 'PoD Falling Bridge', obj=RoomObject(0x1FA5F8, [0x43, 0xDB, 0xFA])),
+ Pot(232, 19, PotItem.Nothing, 'PoD Harmless Hellway', obj=RoomObject(0x1FA64C, [0xD3, 0x9F, 0xFA])),
+ Pot(212, 19, PotItem.Nothing, 'PoD Harmless Hellway', obj=RoomObject(0x1FA649, [0xAB, 0x9F, 0xFA]))],
+ 0x1B: [Pot(20, 23, PotItem.FiveArrows, 'PoD Mimics 2', obj=RoomObject(0x1FAAFE, [0x2B, 0xBB, 0xFA])),
+ Pot(40, 23, PotItem.FiveArrows, 'PoD Mimics 2', obj=RoomObject(0x1FAB01, [0x53, 0xBB, 0xFA]))],
+ 0x1E: [Pot(84, 9, PotItem.Bomb, 'Ice Bomb Drop', obj=RoomObject(0x1FC325, [0xAB, 0x4B, 0xFA]))],
+ 0x1F: [Pot(28, 25, PotItem.Switch, 'Ice Pengator Switch', obj=RoomObject(0x1FC38B, [0x3B, 0xCB, 0xFA])),
+ Pot(28, 23, PotItem.Nothing, 'Ice Pengator Switch', obj=RoomObject(0x1FC388, [0x3B, 0xBB, 0xFA])),
+ Pot(86, 26, PotItem.Nothing, 'Ice Big Key', obj=RoomObject(0x1FC397, [0xAF, 0xD3, 0xFA])),
+ Pot(86, 27, PotItem.Nothing, 'Ice Big Key', obj=RoomObject(0x1FC39A, [0xAF, 0xDB, 0xFA]))],
+ 0x21: [Pot(160, 20, PotItem.Nothing, 'Sewers Key Rat', obj=RoomObject(0x0A8C71, [0x43, 0xA7, 0xFA])),
+ Pot(168, 24, PotItem.SmallMagic, 'Sewers Key Rat', obj=RoomObject(0x0A8C7A, [0x53, 0xC7, 0xFA])),
+ Pot(48, 28, PotItem.Heart, 'Sewers Key Rat', obj=RoomObject(0x0A8C80, [0x63, 0xE3, 0xFA])),
+ Pot(82, 28, PotItem.SmallMagic, 'Sewers Key Rat', obj=RoomObject(0x0A8C7D, [0xA7, 0xE3, 0xFA])),
+ Pot(100, 28, PotItem.Nothing, 'Sewers Key Rat', obj=RoomObject(0x0A8C74, [0xCB, 0xE3, 0xFA])),
+ Pot(104, 28, PotItem.Nothing, 'Sewers Key Rat', obj=RoomObject(0x0A8C77, [0xD3, 0xE3, 0xFA]))],
+ 0x23: [Pot(86, 26, PotItem.OneRupee, 'TR Lazy Eyes', obj=RoomObject(0x1FED09, [0xAF, 0xD3, 0xFA])),
+ Pot(90, 26, PotItem.Heart, 'TR Lazy Eyes', obj=RoomObject(0x1FED0C, [0xB7, 0xD3, 0xFA])),
+ Pot(94, 26, PotItem.OneRupee, 'TR Lazy Eyes', obj=RoomObject(0x1FED0F, [0xBF, 0xD3, 0xFA])),
+ Pot(98, 26, PotItem.Bomb, 'TR Lazy Eyes', obj=RoomObject(0x1FED12, [0xC7, 0xD3, 0xFA])),
+ Pot(102, 26, PotItem.OneRupee, 'TR Lazy Eyes', obj=RoomObject(0x1FED15, [0xCF, 0xD3, 0xFA]))],
+ 0x24: [Pot(12, 4, PotItem.FiveRupees, 'TR Twin Pokeys', obj=RoomObject(0x1FE646, [0x1B, 0x23, 0xFA])),
+ Pot(48, 4, PotItem.Heart, 'TR Twin Pokeys', obj=RoomObject(0x1FE649, [0x63, 0x23, 0xFA])),
+ Pot(12, 12, PotItem.SmallMagic, 'TR Twin Pokeys', obj=RoomObject(0x1FE64C, [0x1B, 0x63, 0xFA])),
+ Pot(48, 12, PotItem.OneRupee, 'TR Twin Pokeys', obj=RoomObject(0x1FE64F, [0x63, 0x63, 0xFA]))],
+ 0x26: [Pot(28, 4, PotItem.Bomb, 'Swamp Shooters', obj=RoomObject(0x1F9BDD, [0x3B, 0x23, 0xFA])),
+ Pot(12, 8, PotItem.SmallMagic, 'Swamp Shooters', obj=RoomObject(0x1F9BDA, [0x1B, 0x43, 0xFA])),
+ Pot(150, 19, PotItem.Switch, 'Swamp Push Statue', obj=RoomObject(0x1F9C46, [0x2F, 0x9F, 0xFA])),
+ Pot(22, 26, PotItem.FiveRupees, 'Swamp Push Statue', obj=RoomObject(0x1F9C49, [0x2F, 0xD3, 0xFA])),
+ Pot(220, 26, PotItem.FiveArrows, 'Swamp Push Statue', PotFlags.SwitchLogicChange, obj=RoomObject(0x1F9C52, [0xBB, 0xD7, 0xFA]))],
+ 0x27: [Pot(214, 19, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCE19, [0xAF, 0x9F, 0xFA])),
+ Pot(214, 20, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCE1C, [0xAF, 0xA7, 0xFA])),
+ Pot(166, 20, PotItem.Bomb, 'Hera 4F', obj=RoomObject(0x1FCE28, [0x4F, 0xA7, 0xFA])),
+ Pot(214, 21, PotItem.Heart, 'Hera 4F', obj=RoomObject(0x1FCE1F, [0xAF, 0xAF, 0xFA])),
+ Pot(40, 28, PotItem.OneRupee, 'Hera 4F', obj=RoomObject(0x1FCE4C, [0x53, 0xE3, 0xFA])),
+ Pot(44, 28, PotItem.OneRupee, 'Hera 4F', obj=RoomObject(0x1FCE4F, [0x5B, 0xE3, 0xFA])),
+ Pot(80, 28, PotItem.FiveRupees, 'Hera 4F', obj=RoomObject(0x1FCE52, [0xA3, 0xE3, 0xFA])),
+ Pot(84, 28, PotItem.FiveRupees, 'Hera 4F', obj=RoomObject(0x1FCE55, [0xAB, 0xE3, 0xFA])),
+ Pot(102, 17, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCE07, [0xCF, 0x8B, 0xFA])),
+ Pot(98, 17, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCE04, [0xC7, 0x8B, 0xFA])),
+ Pot(106, 17, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCE0A, [0xD7, 0x8B, 0xFA])),
+ Pot(166, 21, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCE2B, [0x4F, 0xAF, 0xFA])),
+ Pot(166, 19, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCE25, [0x4F, 0x9F, 0xFA])),
+ Pot(92, 12, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCDF8, [0xBB, 0x63, 0xFA])),
+ Pot(160, 12, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCDE0, [0x43, 0x67, 0xFA]))],
+ 0x2A: [Pot(80, 12, PotItem.OneRupee, 'PoD Arena Main', obj=RoomObject(0x1FA57F, [0xA3, 0x63, 0xFA])),
+ Pot(80, 19, PotItem.Heart, 'PoD Arena Main', obj=RoomObject(0x1FA582, [0xA3, 0x9B, 0xFA]))],
+ 0x2B: [Pot(16, 5, PotItem.Heart, 'PoD Sexy Statue', obj=RoomObject(0x1FAA14, [0x23, 0x2B, 0xFA])),
+ Pot(44, 5, PotItem.Switch, 'PoD Sexy Statue', obj=RoomObject(0x1FAA1D, [0x5B, 0x2B, 0xFA])),
+ Pot(16, 6, PotItem.Heart, 'PoD Sexy Statue', obj=RoomObject(0x1FAA17, [0x23, 0x33, 0xFA])),
+ Pot(44, 6, PotItem.Bomb, 'PoD Sexy Statue', obj=RoomObject(0x1FAA20, [0x5B, 0x33, 0xFA])),
+ Pot(16, 7, PotItem.Heart, 'PoD Sexy Statue', obj=RoomObject(0x1FAA1A, [0x23, 0x3B, 0xFA])),
+ Pot(44, 7, PotItem.Bomb, 'PoD Sexy Statue', obj=RoomObject(0x1FAA23, [0x5B, 0x3B, 0xFA])),
+ Pot(146, 21, PotItem.Bomb, 'PoD Map Balcony', obj=RoomObject(0x1FAA44, [0x27, 0xAF, 0xFA])),
+ Pot(170, 21, PotItem.FiveArrows, 'PoD Map Balcony', obj=RoomObject(0x1FAA4A, [0x57, 0xAF, 0xFA])),
+ Pot(146, 22, PotItem.Bomb, 'PoD Map Balcony', obj=RoomObject(0x1FAA47, [0x27, 0xB7, 0xFA])),
+ Pot(170, 22, PotItem.FiveArrows, 'PoD Map Balcony', obj=RoomObject(0x1FAA4D, [0x57, 0xB7, 0xFA]))],
+ 0x2C: [Pot(108, 24, PotItem.Heart, 'Hookshot Cave (Middle)', obj=RoomObject(0x0A889F, [0xDB, 0xC3, 0xFA])),
+ Pot(112, 24, PotItem.Heart, 'Hookshot Cave (Middle)', obj=RoomObject(0x0A88A2, [0xE3, 0xC3, 0xFA]))],
+ 0x2F: [Pot(28, 7, PotItem.Heart, 'Kakariko Well (back)', obj=RoomObject(0x0A8744, [0x3B, 0x3B, 0xFA])),
+ Pot(32, 7, PotItem.Heart, 'Kakariko Well (back)', obj=RoomObject(0x0A8747, [0x43, 0x3B, 0xFA])),
+ Pot(28, 9, PotItem.FiveRupees, 'Kakariko Well (back)', obj=RoomObject(0x0A874A, [0x3B, 0x4B, 0xFA])),
+ Pot(32, 9, PotItem.FiveRupees, 'Kakariko Well (back)', obj=RoomObject(0x0A874D, [0x43, 0x4B, 0xFA])),
+ Pot(172, 19, PotItem.FiveRupees, 'Kakariko Well (top)', obj=RoomObject(0x0A8762, [0x5B, 0x9F, 0xFA])),
+ Pot(180, 19, PotItem.FiveRupees, 'Kakariko Well (top)', obj=RoomObject(0x0A8765, [0x6B, 0x9F, 0xFA])),
+ Pot(104, 27, PotItem.Heart, 'Kakariko Well (bottom)', obj=RoomObject(0x0A8771, [0xD3, 0xDB, 0xFA])),
+ Pot(104, 28, PotItem.Heart, 'Kakariko Well (bottom)', obj=RoomObject(0x0A8774, [0xD3, 0xE3, 0xFA]))],
+ 0x31: [Pot(92, 28, PotItem.Bomb, 'Hera Beetles', obj=RoomObject(0x1FCF10, [0xBB, 0xE3, 0xFA])),
+ Pot(96, 28, PotItem.Nothing, 'Hera Beetles', obj=RoomObject(0x1FCF13, [0xC3, 0xE3, 0xFA]))],
+ 0x32: [Pot(28, 13, PotItem.SmallMagic, 'Sewers Dark Cross', obj=RoomObject(0x0A8E12, [0x3B, 0x6B, 0xFA]))],
+ 0x34: [Pot(78, 8, PotItem.FiveRupees, 'Swamp Barrier Ledge', obj=RoomObject(0x1F98A3, [0x9F, 0x43, 0xFA])),
+ Pot(92, 8, PotItem.FiveRupees, 'Swamp Barrier Ledge', obj=RoomObject(0x1F98A6, [0xBB, 0x43, 0xFA]))],
+ 0x35: [Pot(60, 6, PotItem.Key, 'Swamp Trench 2 Alcove', obj=RoomObject(0x1F979A, [0x7B, 0x33, 0xFA])),
+ Pot(20, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge', obj=RoomObject(0x1F9725, [0x2B, 0x43, 0xFA])),
+ Pot(24, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge', obj=RoomObject(0x1F9728, [0x33, 0x43, 0xFA])),
+ Pot(28, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge', obj=RoomObject(0x1F972B, [0x3B, 0x43, 0xFA])),
+ Pot(32, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge', obj=RoomObject(0x1F972E, [0x43, 0x43, 0xFA])),
+ Pot(36, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge', obj=RoomObject(0x1F9731, [0x4B, 0x43, 0xFA])),
+ Pot(48, 20, PotItem.Heart, 'Swamp Trench 2 Departure', obj=RoomObject(0x1F9764, [0x63, 0xA3, 0xFA])),
+ Pot(76, 23, PotItem.Nothing, 'Swamp Trench 2 Pots', obj=RoomObject(0x1F976A, [0x9B, 0xBB, 0xFA])),
+ Pot(88, 23, PotItem.Nothing, 'Swamp Trench 2 Pots', obj=RoomObject(0x1F9776, [0xB3, 0xBB, 0xFA])),
+ Pot(100, 27, PotItem.Nothing, 'Swamp Trench 2 Pots', obj=RoomObject(0x1F9773, [0xCB, 0xDB, 0xFA])),
+ Pot(242, 28, PotItem.Nothing, 'Swamp Trench 2 Pots', obj=RoomObject(0x1F9779, [0xE7, 0xE7, 0xFA])),
+ Pot(240, 22, PotItem.Heart, 'Swamp Trench 2 Pots', obj=RoomObject(0x1F9770, [0xE3, 0xB7, 0xFA])),
+ Pot(76, 28, PotItem.Heart, 'Swamp Trench 2 Pots', obj=RoomObject(0x1F976D, [0x9B, 0xE3, 0xFA]))],
+ 0x36: [Pot(108, 4, PotItem.Bomb, 'Swamp Hub Dead Ledge', obj=RoomObject(0x1F9631, [0xDB, 0x23, 0xFA])),
+ Pot(112, 4, PotItem.FiveRupees, 'Swamp Hub Dead Ledge', obj=RoomObject(0x1F9634, [0xE3, 0x23, 0xFA])),
+ Pot(10, 16, PotItem.Heart, 'Swamp Hub Side Ledges', obj=RoomObject(0x1F9625, [0x17, 0x83, 0xFA])),
+ Pot(154, 15, PotItem.Nothing, 'Swamp Hub Side Ledges', obj=RoomObject(0x1F9628, [0x37, 0x7F, 0xFA])),
+ Pot(114, 16, PotItem.Key, 'Swamp Hub Side Ledges', obj=RoomObject(0x1F963A, [0xE7, 0x83, 0xFA])),
+ Pot(222, 15, PotItem.Nothing, 'Swamp Hub Side Ledges', obj=RoomObject(0x1F9637, [0xBF, 0x7F, 0xFA])),
+ Pot(188, 5, PotItem.Nothing, 'Swamp Hub North Ledge', obj=RoomObject(0x1F962B, [0x7B, 0x2F, 0xFA])),
+ Pot(192, 5, PotItem.Nothing, 'Swamp Hub North Ledge', obj=RoomObject(0x1F962E, [0x83, 0x2F, 0xFA]))],
+ 0x37: [Pot(60, 6, PotItem.Key, 'Swamp Trench 1 Alcove', obj=RoomObject(0x1F944A, [0x7B, 0x33, 0xFA])),
+ Pot(48, 20, PotItem.Nothing, 'Swamp Trench 1 Key Ledge', obj=RoomObject(0x1F9423, [0x63, 0xA3, 0xFA]))],
+ 0x38: [Pot(164, 12, PotItem.Bomb, 'Swamp Pot Row', obj=RoomObject(0x1F933A, [0x4B, 0x67, 0xFA])),
+ Pot(164, 13, PotItem.FiveRupees, 'Swamp Pot Row', obj=RoomObject(0x1F933D, [0x4B, 0x6F, 0xFA])),
+ Pot(164, 18, PotItem.Bomb, 'Swamp Pot Row', obj=RoomObject(0x1F9340, [0x4B, 0x97, 0xFA])),
+ Pot(164, 19, PotItem.Key, 'Swamp Pot Row', obj=RoomObject(0x1F9343, [0x4B, 0x9F, 0xFA]))],
+ 0x39: [Pot(12, 20, PotItem.Heart, 'Skull Spike Corner', obj=RoomObject(0x1FC14A, [0x1B, 0xA3, 0xFA])),
+ Pot(48, 28, PotItem.FiveArrows, 'Skull Spike Corner', obj=RoomObject(0x1FC153, [0x63, 0xE3, 0xFA])),
+ Pot(100, 22, PotItem.SmallMagic, 'Skull Final Drop', obj=RoomObject(0x1FC168, [0xCB, 0xB3, 0xFA])),
+ Pot(100, 26, PotItem.FiveArrows, 'Skull Final Drop', obj=RoomObject(0x1FC16B, [0xCB, 0xD3, 0xFA]))],
+ 0x3C: [Pot(24, 8, PotItem.SmallMagic, 'Hookshot Cave (Hook Islands)', obj=RoomObject(0x0A8979, [0x33, 0x43, 0xFA])),
+ Pot(64, 12, PotItem.FiveRupees, 'Hookshot Cave (Hook Islands)', obj=RoomObject(0x0A898E, [0x83, 0x63, 0xFA])),
+ Pot(20, 14, PotItem.OneRupee, 'Hookshot Cave (Hook Islands)', obj=RoomObject(0x0A897C, [0x2B, 0x73, 0xFA])),
+ Pot(20, 19, PotItem.Nothing, 'Hookshot Cave (Hook Islands)', obj=RoomObject(0x0A897F, [0x2B, 0x9B, 0xFA])),
+ Pot(68, 18, PotItem.FiveRupees, 'Hookshot Cave (Bonk Islands)', obj=RoomObject(0x0A8994, [0x8B, 0x93, 0xFA])),
+ Pot(96, 19, PotItem.Heart, 'Hookshot Cave (Front)', obj=RoomObject(0x0A8976, [0xC3, 0x9B, 0xFA])),
+ Pot(64, 20, PotItem.FiveRupees, 'Hookshot Cave (Bonk Islands)', obj=RoomObject(0x0A8997, [0x83, 0xA3, 0xFA])),
+ Pot(64, 26, PotItem.FiveRupees, 'Hookshot Cave (Bonk Islands)', obj=RoomObject(0x0A899A, [0x83, 0xD3, 0xFA]))],
+ 0x3D: [Pot(76, 12, PotItem.Bomb, 'GT Mini Helmasaur Room', obj=RoomObject(0x1FFC9B, [0x9B, 0x63, 0xFA])),
+ Pot(112, 12, PotItem.Bomb, 'GT Mini Helmasaur Room', obj=RoomObject(0x1FFC9E, [0xE3, 0x63, 0xFA])),
+ Pot(24, 22, PotItem.Heart, 'GT Crystal Inner Circle', obj=RoomObject(0x1FFCE3, [0x33, 0xB3, 0xFA])),
+ Pot(40, 22, PotItem.FiveArrows, 'GT Crystal Inner Circle', obj=RoomObject(0x1FFCE6, [0x53, 0xB3, 0xFA])),
+ Pot(32, 24, PotItem.Heart, 'GT Crystal Inner Circle', obj=RoomObject(0x1FFCF8, [0x43, 0xC3, 0xFA])),
+ Pot(20, 26, PotItem.FiveRupees, 'GT Crystal Inner Circle', obj=RoomObject(0x1FFCFB, [0x2B, 0xD3, 0xFA])),
+ Pot(36, 26, PotItem.BigMagic, 'GT Crystal Inner Circle', obj=RoomObject(0x1FFCFE, [0x4B, 0xD3, 0xFA]))],
+ 0x3E: [Pot(96, 6, PotItem.Bomb, 'Ice Stalfos Hint', obj=RoomObject(0x1FC41D, [0xC3, 0x33, 0xFA])),
+ Pot(100, 6, PotItem.SmallMagic, 'Ice Stalfos Hint', obj=RoomObject(0x1FC420, [0xCB, 0x33, 0xFA])),
+ Pot(88, 10, PotItem.Heart, 'Ice Stalfos Hint', obj=RoomObject(0x1FC429, [0xB3, 0x53, 0xFA])),
+ Pot(92, 10, PotItem.SmallMagic, 'Ice Stalfos Hint', obj=RoomObject(0x1FC42C, [0xBB, 0x53, 0xFA]))],
+ 0x3F: [Pot(12, 25, PotItem.OneRupee, 'Ice Hammer Block', obj=RoomObject(0x1FC4DC, [0x1B, 0xCB, 0xFA])),
+ Pot(20, 25, PotItem.OneRupee, 'Ice Hammer Block', obj=RoomObject(0x1FC4E5, [0x2B, 0xCB, 0xFA])),
+ Pot(12, 26, PotItem.Bomb, 'Ice Hammer Block', obj=RoomObject(0x1FC4DF, [0x1B, 0xD3, 0xFA])),
+ Pot(20, 26, PotItem.Bomb, 'Ice Hammer Block', obj=RoomObject(0x1FC4E8, [0x2B, 0xD3, 0xFA])),
+ Pot(12, 27, PotItem.Switch, 'Ice Hammer Block', obj=RoomObject(0x1FC4E2, [0x1B, 0xDB, 0xFA])),
+ Pot(20, 27, PotItem.Heart, 'Ice Hammer Block', obj=RoomObject(0x1FC4EB, [0x2B, 0xDB, 0xFA])),
+ Pot(28, 23, PotItem.Key, 'Ice Hammer Block', PotFlags.Block)],
+ 0x41: [Pot(100, 10, PotItem.Heart, 'Sewers Behind Tapestry', obj=RoomObject(0x0A8EDC, [0xCB, 0x53, 0xFA])),
+ Pot(52, 15, PotItem.OneRupee, 'Sewers Behind Tapestry', obj=RoomObject(0x0A8EDF, [0x6B, 0x7B, 0xFA])),
+ Pot(52, 16, PotItem.SmallMagic, 'Sewers Behind Tapestry', obj=RoomObject(0x0A8EE2, [0x6B, 0x83, 0xFA])),
+ Pot(148, 22, PotItem.SmallMagic, 'Sewers Behind Tapestry', obj=RoomObject(0x0A8EE5, [0x2B, 0xB7, 0xFA]))],
+ 0x43: [Pot(66, 4, PotItem.FiveArrows, 'Desert Wall Slide', obj=RoomObject(0x1F87BC, [0x87, 0x23, 0xFA])),
+ Pot(78, 4, PotItem.SmallMagic, 'Desert Wall Slide', obj=RoomObject(0x1F87BF, [0x9F, 0x23, 0xFA])),
+ Pot(66, 9, PotItem.Heart, 'Desert Wall Slide', obj=RoomObject(0x1F87C2, [0x87, 0x4B, 0xFA])),
+ Pot(78, 9, PotItem.Heart, 'Desert Wall Slide', obj=RoomObject(0x1F87C5, [0x9F, 0x4B, 0xFA])),
+ Pot(112, 28, PotItem.Nothing, 'Desert Tiles 2', obj=RoomObject(0x1F87DA, [0xE3, 0xE3, 0xFA])),
+ Pot(76, 28, PotItem.Nothing, 'Desert Tiles 2', obj=RoomObject(0x1F87D7, [0x9B, 0xE3, 0xFA])),
+ Pot(76, 20, PotItem.Nothing, 'Desert Tiles 2', obj=RoomObject(0x1F87D1, [0x9B, 0xA3, 0xFA])),
+ Pot(112, 20, PotItem.Key, 'Desert Tiles 2', obj=RoomObject(0x1F87D4, [0xE3, 0xA3, 0xFA]))],
+ 0x44: [Pot(204, 7, PotItem.Nothing, 'Thieves Conveyor Bridge', PotFlags.Block)],
+ 0x45: [Pot(12, 4, PotItem.FiveArrows, 'Thieves Basement Block', obj=RoomObject(0x1FDC2A, [0x1B, 0x23, 0xFA])),
+ Pot(48, 12, PotItem.FiveArrows, 'Thieves Basement Block', obj=RoomObject(0x1FDC2D, [0x63, 0x63, 0xFA])),
+ Pot(92, 11, PotItem.Nothing, "Thieves Blind's Cell Interior", obj=RoomObject(0x1FDC4E, [0xBB, 0x5B, 0xFA])),
+ Pot(108, 11, PotItem.Heart, "Thieves Blind's Cell Interior", obj=RoomObject(0x1FDC51, [0xDB, 0x5B, 0xFA])),
+ Pot(220, 16, PotItem.SmallMagic, "Thieves Blind's Cell Interior", obj=RoomObject(0x1FDC54, [0xBB, 0x87, 0xFA])),
+ Pot(236, 16, PotItem.Heart, "Thieves Blind's Cell Interior", obj=RoomObject(0x1FDC57, [0xDB, 0x87, 0xFA])),
+ Pot(0x9C, 7, PotItem.Nothing, 'Thieves Basement Block', PotFlags.Block)],
+ 0x46: [Pot(96, 5, PotItem.Heart, 'Swamp Donut Top', obj=RoomObject(0x1F9B91, [0xC3, 0x2B, 0xFA])),
+ Pot(28, 27, PotItem.Heart, 'Swamp Donut Bottom', obj=RoomObject(0x1F9B94, [0x3B, 0xDB, 0xFA]))],
+ 0x49: [Pot(104, 15, PotItem.SmallMagic, 'Skull Torch Room', obj=RoomObject(0x1FC102, [0xD3, 0x7B, 0xFA])),
+ Pot(104, 16, PotItem.SmallMagic, 'Skull Torch Room', obj=RoomObject(0x1FC105, [0xD3, 0x83, 0xFA])),
+ Pot(156, 27, PotItem.Nothing, 'Skull Star Pits', obj=RoomObject(0x1FC063, [0x3B, 0xDF, 0xFA])),
+ Pot(172, 24, PotItem.Nothing, 'Skull Star Pits', obj=RoomObject(0x1FC084, [0x5B, 0xC7, 0xFA])),
+ Pot(172, 23, PotItem.Nothing, 'Skull Star Pits', obj=RoomObject(0x1FC081, [0x5B, 0xBF, 0xFA])),
+ Pot(144, 20, PotItem.Nothing, 'Skull Star Pits', obj=RoomObject(0x1FC04E, [0x23, 0xA7, 0xFA])),
+ Pot(144, 19, PotItem.SmallMagic, 'Skull Star Pits', obj=RoomObject(0x1FC04B, [0x23, 0x9F, 0xFA])),
+ Pot(172, 20, PotItem.Heart, 'Skull Star Pits', obj=RoomObject(0x1FC07E, [0x5B, 0xA7, 0xFA])),
+ Pot(144, 27, PotItem.Heart, 'Skull Star Pits', obj=RoomObject(0x1FC051, [0x23, 0xDF, 0xFA])),
+ Pot(172, 28, PotItem.SmallMagic, 'Skull Star Pits', obj=RoomObject(0x1FC090, [0x5B, 0xE7, 0xFA])),
+ Pot(160, 27, PotItem.Nothing, 'Skull Star Pits', obj=RoomObject(0x1FC066, [0x43, 0xDF, 0xFA]))],
+ 0x4A: [Pot(14, 5, PotItem.Switch, 'PoD Left Cage', obj=RoomObject(0x1FA1C2, [0x1F, 0x2B, 0xFA])),
+ Pot(32, 5, PotItem.Bomb, 'PoD Left Cage', obj=RoomObject(0x1FA1C5, [0x43, 0x2B, 0xFA])),
+ Pot(14, 11, PotItem.Heart, 'PoD Left Cage', obj=RoomObject(0x1FA1C8, [0x1F, 0x5B, 0xFA])),
+ Pot(32, 11, PotItem.OneRupee, 'PoD Left Cage', obj=RoomObject(0x1FA1CB, [0x43, 0x5B, 0xFA])),
+ Pot(56, 8, PotItem.Bomb, 'PoD Middle Cage', obj=RoomObject(0x1FA1D1, [0x73, 0x43, 0xFA])),
+ Pot(68, 8, PotItem.Bomb, 'PoD Middle Cage', obj=RoomObject(0x1FA1D4, [0x8B, 0x43, 0xFA])),
+ Pot(92, 5, PotItem.Bomb, 'PoD Middle Cage', obj=RoomObject(0x1FA1DA, [0xBB, 0x2B, 0xFA])),
+ Pot(110, 5, PotItem.Switch, 'PoD Middle Cage', obj=RoomObject(0x1FA1DD, [0xDF, 0x2B, 0xFA])),
+ Pot(92, 11, PotItem.OneRupee, 'PoD Middle Cage', obj=RoomObject(0x1FA1E0, [0xBB, 0x5B, 0xFA])),
+ Pot(110, 11, PotItem.Heart, 'PoD Middle Cage', obj=RoomObject(0x1FA1E3, [0xDF, 0x5B, 0xFA]))],
+ 0x4B: [Pot(20, 6, PotItem.FiveArrows, 'PoD Mimics 1', obj=RoomObject(0x1FA83C, [0x2B, 0x33, 0xFA])),
+ Pot(40, 6, PotItem.Heart, 'PoD Mimics 1', obj=RoomObject(0x1FA83F, [0x53, 0x33, 0xFA]))],
+ 0x4E: [Pot(140, 7, PotItem.Nothing, 'Ice Bomb Jump Catwalk', obj=RoomObject(0x1FC57E, [0x1B, 0x3F, 0xFA])),
+ Pot(48, 10, PotItem.Nothing, 'Ice Bomb Jump Catwalk', obj=RoomObject(0x1FC587, [0x63, 0x53, 0xFA])),
+ Pot(140, 11, PotItem.Switch, 'Ice Bomb Jump Catwalk', obj=RoomObject(0x1FC581, [0x1B, 0x5F, 0xFA])),
+ Pot(28, 12, PotItem.Heart, 'Ice Bomb Jump Catwalk', obj=RoomObject(0x1FC584, [0x3B, 0x63, 0xFA])),
+ Pot(112, 12, PotItem.SmallMagic, 'Ice Narrow Corridor', obj=RoomObject(0x1FC58A, [0xE3, 0x63, 0xFA]))],
+ 0x50: [Pot(96, 0x6, PotItem.Heart, 'Hyrule Castle West Hall', PotFlags.LowerRegion, obj=RoomObject(0x0A9099, [0xC3, 0x33, 0xFA])),
+ Pot(100, 0x6, PotItem.Heart, 'Hyrule Castle West Hall', PotFlags.LowerRegion, obj=RoomObject(0x0A909C, [0xCB, 0x33, 0xFA]))],
+ 0x52: [Pot(138, 3, PotItem.Heart, 'Hyrule Castle East Hall', obj=RoomObject(0x0A91C7, [0x17, 0x1F, 0xFA])),
+ Pot(194, 26, PotItem.Heart, 'Hyrule Castle East Hall', obj=RoomObject(0x0A91CA, [0x87, 0xD7, 0xFA]))],
+ 0x53: [Pot(92, 11, PotItem.Heart, 'Desert Beamos Hall', obj=RoomObject(0x1F8844, [0xBB, 0x5B, 0xFA])),
+ Pot(96, 11, PotItem.SmallMagic, 'Desert Beamos Hall', obj=RoomObject(0x1F8847, [0xC3, 0x5B, 0xFA])),
+ Pot(100, 11, PotItem.Key, 'Desert Beamos Hall', obj=RoomObject(0x1F884A, [0xCB, 0x5B, 0xFA])),
+ Pot(104, 11, PotItem.Heart, 'Desert Beamos Hall', obj=RoomObject(0x1F884D, [0xD3, 0x5B, 0xFA]))],
+ 0x54: [Pot(186, 25, PotItem.FiveRupees, 'Swamp Attic', obj=RoomObject(0x1F9A28, [0x77, 0xCF, 0xFA])),
+ Pot(186, 26, PotItem.Heart, 'Swamp Attic', obj=RoomObject(0x1F9A2B, [0x77, 0xD7, 0xFA])),
+ Pot(186, 27, PotItem.Heart, 'Swamp Attic', obj=RoomObject(0x1F9A2E, [0x77, 0xDF, 0xFA])),
+ Pot(186, 28, PotItem.Heart, 'Swamp Attic', obj=RoomObject(0x1F9A31, [0x77, 0xE7, 0xFA]))],
+ 0x55: [Pot(230, 24, PotItem.SmallMagic, 'Hyrule Castle Secret Entrance', obj=RoomObject(0x0A8127, [0xCF, 0xC7, 0xFA])),
+ Pot(230, 25, PotItem.SmallMagic, 'Hyrule Castle Secret Entrance', obj=RoomObject(0x0A812A, [0xCF, 0xCF, 0xFA]))],
+ 0x56: [Pot(100, 6, PotItem.Nothing, 'Skull Back Drop', obj=RoomObject(0x1FBADC, [0xCB, 0x33, 0xFA])),
+ Pot(96, 10, PotItem.Nothing, 'Skull Back Drop', obj=RoomObject(0x1FBAEE, [0xC3, 0x53, 0xFA])),
+ Pot(92, 10, PotItem.Nothing, 'Skull Back Drop', obj=RoomObject(0x1FBAEB, [0xBB, 0x53, 0xFA])),
+ Pot(20, 6, PotItem.SmallMagic, 'Skull X Room', obj=RoomObject(0x1FBB1E, [0x2B, 0x33, 0xFA])),
+ Pot(40, 6, PotItem.SmallMagic, 'Skull X Room', obj=RoomObject(0x1FBB27, [0x53, 0x33, 0xFA])),
+ Pot(24, 7, PotItem.SmallMagic, 'Skull X Room', obj=RoomObject(0x1FBB21, [0x33, 0x3B, 0xFA])),
+ Pot(36, 7, PotItem.SmallMagic, 'Skull X Room', obj=RoomObject(0x1FBB24, [0x4B, 0x3B, 0xFA])),
+ Pot(12, 8, PotItem.Heart, 'Skull X Room', obj=RoomObject(0x1FBB12, [0x1B, 0x43, 0xFA])),
+ Pot(48, 8, PotItem.Heart, 'Skull X Room', obj=RoomObject(0x1FBB1B, [0x63, 0x43, 0xFA])),
+ Pot(24, 9, PotItem.SmallMagic, 'Skull X Room', obj=RoomObject(0x1FBB2A, [0x33, 0x4B, 0xFA])),
+ Pot(36, 9, PotItem.SmallMagic, 'Skull X Room', obj=RoomObject(0x1FBB30, [0x4B, 0x4B, 0xFA])),
+ Pot(20, 10, PotItem.FiveRupees, 'Skull X Room', obj=RoomObject(0x1FBB2D, [0x2B, 0x53, 0xFA])),
+ Pot(40, 10, PotItem.FiveRupees, 'Skull X Room', obj=RoomObject(0x1FBB33, [0x53, 0x53, 0xFA])),
+ Pot(12, 20, PotItem.Key, 'Skull 2 West Lobby', obj=RoomObject(0x1FBAC1, [0x1B, 0xA3, 0xFA])),
+ Pot(48, 20, PotItem.Nothing, 'Skull 2 West Lobby', obj=RoomObject(0x1FBAB8, [0x63, 0xA3, 0xFA]))],
+ 0x57: [Pot(92, 7, PotItem.BigMagic, 'Skull Lone Pot', obj=RoomObject(0x1FBB6F, [0xBB, 0x3B, 0xFA])),
+ Pot(32, 4, PotItem.Nothing, 'Skull Big Key', obj=RoomObject(0x1FBB72, [0x43, 0x23, 0xFA])),
+ Pot(92, 23, PotItem.Bomb, 'Skull Pot Prison', obj=RoomObject(0x1FBBB4, [0xBB, 0xBB, 0xFA])),
+ Pot(100, 23, PotItem.SmallMagic, 'Skull Pot Prison', obj=RoomObject(0x1FBBB7, [0xCB, 0xBB, 0xFA])),
+ Pot(84, 25, PotItem.FiveRupees, 'Skull Pot Prison', obj=RoomObject(0x1FBBB1, [0xAB, 0xCB, 0xFA])),
+ Pot(76, 27, PotItem.Heart, 'Skull Pot Prison', obj=RoomObject(0x1FBBAE, [0x9B, 0xDB, 0xFA])),
+ Pot(12, 20, PotItem.SmallMagic, 'Skull 2 East Lobby', obj=RoomObject(0x1FBB93, [0x1B, 0xA3, 0xFA])),
+ Pot(48, 20, PotItem.SmallMagic, 'Skull 2 East Lobby', obj=RoomObject(0x1FBB99, [0x63, 0xA3, 0xFA])),
+ Pot(30, 22, PotItem.Switch, 'Skull 2 East Lobby', obj=RoomObject(0x1FBB96, [0x3F, 0xB3, 0xFA]))],
+ 0x58: [Pot(12, 7, PotItem.SmallMagic, 'Skull Pull Switch', obj=RoomObject(0x1FBC4B, [0x1B, 0x3B, 0xFA])),
+ Pot(16, 7, PotItem.Nothing, 'Skull Pull Switch', obj=RoomObject(0x1FBC4E, [0x23, 0x3B, 0xFA])),
+ Pot(16, 8, PotItem.SmallMagic, 'Skull Pull Switch', obj=RoomObject(0x1FBC54, [0x23, 0x43, 0xFA])),
+ Pot(12, 12, PotItem.Nothing, 'Skull Pull Switch', obj=RoomObject(0x1FBC51, [0x1B, 0x63, 0xFA])),
+ Pot(96, 9, PotItem.Nothing, 'Skull Pot Circle', obj=RoomObject(0x1FBC90, [0xC3, 0x4B, 0xFA])),
+ Pot(92, 8, PotItem.Nothing, 'Skull Pot Circle', obj=RoomObject(0x1FBC8D, [0xBB, 0x43, 0xFA])),
+ Pot(108, 8, PotItem.Nothing, 'Skull Pot Circle', obj=RoomObject(0x1FBC9F, [0xDB, 0x43, 0xFA])),
+ Pot(108, 6, PotItem.Nothing, 'Skull Pot Circle', obj=RoomObject(0x1FBC99, [0xDB, 0x33, 0xFA])),
+ Pot(104, 5, PotItem.Nothing, 'Skull Pot Circle', obj=RoomObject(0x1FBC84, [0xD3, 0x2B, 0xFA])),
+ Pot(92, 6, PotItem.Nothing, 'Skull Pot Circle', obj=RoomObject(0x1FBC87, [0xBB, 0x33, 0xFA])),
+ Pot(96, 5, PotItem.Bomb, 'Skull Pot Circle', obj=RoomObject(0x1FBC7E, [0xC3, 0x2B, 0xFA])),
+ Pot(100, 5, PotItem.SmallMagic, 'Skull Pot Circle', obj=RoomObject(0x1FBC81, [0xCB, 0x2B, 0xFA])),
+ Pot(92, 7, PotItem.Heart, 'Skull Pot Circle', obj=RoomObject(0x1FBC8A, [0xBB, 0x3B, 0xFA])),
+ Pot(108, 7, PotItem.Heart, 'Skull Pot Circle', obj=RoomObject(0x1FBC9C, [0xDB, 0x3B, 0xFA])),
+ Pot(100, 9, PotItem.SmallMagic, 'Skull Pot Circle', obj=RoomObject(0x1FBC93, [0xCB, 0x4B, 0xFA])),
+ Pot(104, 9, PotItem.Bomb, 'Skull Pot Circle', obj=RoomObject(0x1FBC96, [0xD3, 0x4B, 0xFA]))],
+ 0x59: [Pot(26, 0xb, PotItem.Heart, 'Skull 3 Lobby', PotFlags.LowerRegion, obj=RoomObject(0x1FBFC0, [0x37, 0x5B, 0xFA])),
+ Pot(32, 8, PotItem.Nothing, 'Skull 3 Lobby', PotFlags.LowerRegion, obj=RoomObject(0x1FBFBD, [0x43, 0x43, 0xFA])),
+ Pot(76, 28, PotItem.Nothing, 'Skull East Bridge', obj=RoomObject(0x1FBF82, [0x9B, 0xE3, 0xFA])),
+ Pot(112, 28, PotItem.Nothing, 'Skull East Bridge', obj=RoomObject(0x1FBF85, [0xE3, 0xE3, 0xFA]))],
+ 0x5B: [Pot(218, 0x5, PotItem.Nothing, 'GT Hidden Spikes', PotFlags.LowerRegion, obj=RoomObject(0x1FF865, [0xB7, 0x2F, 0xFA])),
+ Pot(222, 0x5, PotItem.Switch, 'GT Hidden Spikes', PotFlags.LowerRegion, obj=RoomObject(0x1FF868, [0xBF, 0x2F, 0xFA])),
+ Pot(226, 0x5, PotItem.Nothing, 'GT Hidden Spikes', PotFlags.LowerRegion, obj=RoomObject(0x1FF86B, [0xC7, 0x2F, 0xFA]))],
+ 0x5C: [Pot(228, 25, PotItem.Nothing, 'GT Refill', obj=RoomObject(0x1FF964, [0xCB, 0xCF, 0xFA])),
+ Pot(104, 24, PotItem.Nothing, 'GT Refill', obj=RoomObject(0x1FF967, [0xD3, 0xC3, 0xFA])),
+ Pot(228, 22, PotItem.Nothing, 'GT Refill', obj=RoomObject(0x1FF96A, [0xCB, 0xB7, 0xFA])),
+ Pot(216, 25, PotItem.Nothing, 'GT Refill', obj=RoomObject(0x1FF95E, [0xB3, 0xCF, 0xFA])),
+ Pot(84, 24, PotItem.Nothing, 'GT Refill', obj=RoomObject(0x1FF95B, [0xAB, 0xC3, 0xFA])),
+ Pot(216, 22, PotItem.Nothing, 'GT Refill', obj=RoomObject(0x1FF958, [0xB3, 0xB7, 0xFA])),
+ Pot(94, 22, PotItem.Bomb, 'GT Refill', obj=RoomObject(0x1FF955, [0xBF, 0xB3, 0xFA])),
+ Pot(94, 26, PotItem.BigMagic, 'GT Refill', obj=RoomObject(0x1FF961, [0xBF, 0xD3, 0xFA]))],
+ 0x5D: [Pot(16, 5, PotItem.Bomb, 'GT Gauntlet 2', obj=RoomObject(0x1FF99F, [0x23, 0x2B, 0xFA])),
+ Pot(44, 5, PotItem.FiveRupees, 'GT Gauntlet 2', obj=RoomObject(0x1FF9A2, [0x5B, 0x2B, 0xFA])),
+ Pot(16, 11, PotItem.OneRupee, 'GT Gauntlet 2', obj=RoomObject(0x1FF9A5, [0x23, 0x5B, 0xFA])),
+ Pot(44, 11, PotItem.FiveArrows, 'GT Gauntlet 2', obj=RoomObject(0x1FF9A8, [0x5B, 0x5B, 0xFA])),
+ Pot(12, 20, PotItem.FiveArrows, 'GT Gauntlet 3', obj=RoomObject(0x1FF9C9, [0x1B, 0xA3, 0xFA])),
+ Pot(48, 20, PotItem.Bomb, 'GT Gauntlet 3', obj=RoomObject(0x1FF9CC, [0x63, 0xA3, 0xFA])),
+ Pot(12, 28, PotItem.SmallMagic, 'GT Gauntlet 3', obj=RoomObject(0x1FF9CF, [0x1B, 0xE3, 0xFA])),
+ Pot(48, 28, PotItem.Bomb, 'GT Gauntlet 3', obj=RoomObject(0x1FF9D2, [0x63, 0xE3, 0xFA]))],
+ 0x5E: [Pot(92, 4, PotItem.SmallMagic, 'Ice Falling Square', obj=RoomObject(0x1FC679, [0xBB, 0x23, 0xFA])),
+ Pot(96, 4, PotItem.SmallMagic, 'Ice Falling Square', obj=RoomObject(0x1FC67C, [0xC3, 0x23, 0xFA])),
+ Pot(76, 8, PotItem.Heart, 'Ice Falling Square', obj=RoomObject(0x1FC67F, [0x9B, 0x43, 0xFA])),
+ Pot(112, 8, PotItem.Heart, 'Ice Falling Square', obj=RoomObject(0x1FC688, [0xE3, 0x43, 0xFA]))],
+ 0x5F: [Pot(44, 27, PotItem.Switch, 'Ice Spike Room', obj=RoomObject(0x1FC6E8, [0x5B, 0xDB, 0xFA]))],
+ 0x60: [Pot(76, 4, PotItem.Heart, 'Hyrule Castle West Lobby', obj=RoomObject(0x0A92B2, [0x9B, 0x23, 0xFA])),
+ Pot(112, 4, PotItem.Heart, 'Hyrule Castle West Lobby', obj=RoomObject(0x0A92AF, [0xE3, 0x23, 0xFA]))],
+ 0x62: [Pot(208, 21, PotItem.Heart, 'Hyrule Castle East Lobby', obj=RoomObject(0x0A950E, [0xA3, 0xAF, 0xFA]))],
+ 0x63: [Pot(48, 4, PotItem.Nothing, 'Desert Tiles 1', obj=RoomObject(0x1F88C9, [0x63, 0x23, 0xFA])),
+ Pot(12, 4, PotItem.Nothing, 'Desert Tiles 1', obj=RoomObject(0x1F88C6, [0x1B, 0x23, 0xFA])),
+ Pot(12, 8, PotItem.Nothing, 'Desert Tiles 1', obj=RoomObject(0x1F88CC, [0x1B, 0x43, 0xFA])),
+ Pot(48, 12, PotItem.Nothing, 'Desert Tiles 1', obj=RoomObject(0x1F88D2, [0x63, 0x63, 0xFA])),
+ Pot(48, 8, PotItem.Heart, 'Desert Tiles 1', obj=RoomObject(0x1F88CF, [0x63, 0x43, 0xFA])),
+ Pot(12, 12, PotItem.Key, 'Desert Tiles 1', obj=RoomObject(0x1F88D5, [0x1B, 0x63, 0xFA]))],
+ 0x64: [Pot(12, 22, PotItem.Bomb, 'Thieves Attic Hint', PotFlags.SwitchLogicChange, obj=RoomObject(0x1FD9F9, [0x1B, 0xB3, 0xFA])),
+ Pot(16, 22, PotItem.Bomb, 'Thieves Attic Hint', PotFlags.SwitchLogicChange, obj=RoomObject(0x1FD9FC, [0x23, 0xB3, 0xFA])),
+ Pot(20, 22, PotItem.Bomb, 'Thieves Attic Hint', PotFlags.SwitchLogicChange, obj=RoomObject(0x1FD9FF, [0x2B, 0xB3, 0xFA])),
+ Pot(36, 28, PotItem.Bomb, 'Thieves Attic Switch', obj=RoomObject(0x1FDA1A, [0x4B, 0xE3, 0xFA])),
+ Pot(40, 28, PotItem.SmallMagic, 'Thieves Attic Switch', obj=RoomObject(0x1FDA1D, [0x53, 0xE3, 0xFA])),
+ Pot(44, 28, PotItem.SmallMagic, 'Thieves Attic Switch', obj=RoomObject(0x1FDA20, [0x5B, 0xE3, 0xFA])),
+ Pot(48, 28, PotItem.Switch, 'Thieves Attic Switch', obj=RoomObject(0x1FDA23, [0x63, 0xE3, 0xFA]))],
+ 0x65: [Pot(100, 28, PotItem.Bomb, 'Thieves Attic Window', obj=RoomObject(0x1FDA95, [0xCB, 0xE3, 0xFA])),
+ Pot(104, 28, PotItem.Bomb, 'Thieves Attic Window', obj=RoomObject(0x1FDA98, [0xD3, 0xE3, 0xFA]))],
+ 0x66: [Pot(48, 0x5, PotItem.FiveArrows, 'Swamp Refill', PotFlags.LowerRegion, obj=RoomObject(0x1F9F51, [0x63, 0x2B, 0xFA])),
+ Pot(52, 0x5, PotItem.Bomb, 'Swamp Refill', PotFlags.LowerRegion, obj=RoomObject(0x1F9F57, [0x6B, 0x2B, 0xFA])),
+ Pot(56, 0x5, PotItem.FiveRupees, 'Swamp Refill', PotFlags.LowerRegion, obj=RoomObject(0x1F9F5D, [0x73, 0x2B, 0xFA])),
+ Pot(48, 0x6, PotItem.FiveArrows, 'Swamp Refill', PotFlags.LowerRegion, RoomObject(0x1F9F54, [0x63, 0x33, 0xFA])),
+ Pot(52, 0x6, PotItem.Bomb, 'Swamp Refill', PotFlags.LowerRegion, obj=RoomObject(0x1F9F5A, [0x6B, 0x33, 0xFA])),
+ Pot(56, 0x6, PotItem.FiveRupees, 'Swamp Refill', PotFlags.LowerRegion, obj=RoomObject(0x1F9F60, [0x73, 0x33, 0xFA])),
+ Pot(84, 5, PotItem.Heart, 'Swamp Behind Waterfall', obj=RoomObject(0x1F9F07, [0xAB, 0x2B, 0xFA])),
+ Pot(104, 5, PotItem.FiveArrows, 'Swamp Behind Waterfall', obj=RoomObject(0x1F9F0D, [0xD3, 0x2B, 0xFA])),
+ Pot(84, 6, PotItem.Heart, 'Swamp Behind Waterfall', obj=RoomObject(0x1F9F0A, [0xAB, 0x33, 0xFA])),
+ Pot(104, 6, PotItem.Bomb, 'Swamp Behind Waterfall', obj=RoomObject(0x1F9F10, [0xD3, 0x33, 0xFA]))],
+ 0x67: [Pot(22, 26, PotItem.Nothing, 'Skull Left Drop', obj=RoomObject(0x1FBDDE, [0x2F, 0xD3, 0xFA])),
+ Pot(18, 22, PotItem.Nothing, 'Skull Left Drop', obj=RoomObject(0x1FBDD2, [0x27, 0xB3, 0xFA])),
+ Pot(12, 7, PotItem.FiveArrows, 'Skull Left Drop', obj=RoomObject(0x1FBDCC, [0x1B, 0x3B, 0xFA])),
+ Pot(48, 7, PotItem.SmallMagic, 'Skull Left Drop', obj=RoomObject(0x1FBDCF, [0x63, 0x3B, 0xFA])),
+ Pot(18, 23, PotItem.SmallMagic, 'Skull Left Drop', obj=RoomObject(0x1FBDD5, [0x27, 0xBB, 0xFA])),
+ Pot(18, 26, PotItem.Heart, 'Skull Left Drop', obj=RoomObject(0x1FBDDB, [0x27, 0xD3, 0xFA])),
+ Pot(96, 19, PotItem.Heart, 'Skull Compass Room', obj=RoomObject(0x1FBDE7, [0xC3, 0x9B, 0xFA])),
+ Pot(74, 20, PotItem.SmallMagic, 'Skull Compass Room', obj=RoomObject(0x1FBDEA, [0x97, 0xA3, 0xFA])),
+ Pot(92, 9, PotItem.Nothing, 'Skull Compass Room', obj=RoomObject(0x1FBDE1, [0xBB, 0x4B, 0xFA])),
+ Pot(84, 28, PotItem.Nothing, 'Skull Compass Room', obj=RoomObject(0x1FBDF0, [0xAB, 0xE3, 0xFA])),
+ Pot(104, 28, PotItem.Heart, 'Skull Compass Room', obj=RoomObject(0x1FBDF3, [0xD3, 0xE3, 0xFA]))],
+ 0x68: [Pot(84, 14, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBE8A, [0xAB, 0x73, 0xFA])),
+ Pot(84, 13, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBE87, [0xAB, 0x6B, 0xFA])),
+ Pot(88, 12, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBE84, [0xB3, 0x63, 0xFA])),
+ Pot(88, 6, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBE7E, [0xB3, 0x33, 0xFA])),
+ Pot(88, 5, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBE7B, [0xB3, 0x2B, 0xFA])),
+ Pot(88, 4, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBE78, [0xB3, 0x23, 0xFA])),
+ Pot(64, 17, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBEA5, [0x83, 0x8B, 0xFA])),
+ Pot(64, 15, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBE9F, [0x83, 0x7B, 0xFA])),
+ Pot(64, 7, PotItem.Heart, 'Skull Pinball', obj=RoomObject(0x1FBE75, [0x83, 0x3B, 0xFA])),
+ Pot(88, 7, PotItem.SmallMagic, 'Skull Pinball', obj=RoomObject(0x1FBE81, [0xB3, 0x3B, 0xFA])),
+ Pot(64, 16, PotItem.Heart, 'Skull Pinball', obj=RoomObject(0x1FBEA2, [0x83, 0x83, 0xFA])),
+ Pot(64, 24, PotItem.SmallMagic, 'Skull Pinball', obj=RoomObject(0x1FBEAB, [0x83, 0xC3, 0xFA])),
+ Pot(64, 25, PotItem.Heart, 'Skull Pinball', obj=RoomObject(0x1FBEAE, [0x83, 0xCB, 0xFA]))],
+ 0x6B: [Pot(28, 5, PotItem.Heart, 'GT Crystal Paths', obj=RoomObject(0x1FF7C1, [0x3B, 0x2B, 0xFA])),
+ Pot(44, 5, PotItem.Nothing, 'GT Crystal Paths', obj=RoomObject(0x1FF7C4, [0x5B, 0x2B, 0xFA])),
+ Pot(28, 8, PotItem.Nothing, 'GT Crystal Paths', obj=RoomObject(0x1FF7D0, [0x3B, 0x43, 0xFA])),
+ Pot(44, 8, PotItem.SmallMagic, 'GT Crystal Paths', obj=RoomObject(0x1FF7D3, [0x5B, 0x43, 0xFA])),
+ Pot(28, 11, PotItem.SmallMagic, 'GT Crystal Paths', obj=RoomObject(0x1FF7D6, [0x3B, 0x5B, 0xFA])),
+ Pot(44, 11, PotItem.Nothing, 'GT Crystal Paths', obj=RoomObject(0x1FF7D9, [0x5B, 0x5B, 0xFA])),
+ Pot(90, 25, PotItem.Nothing, 'GT Mimics 2', obj=RoomObject(0x1FF7FD, [0xB7, 0xCB, 0xFA])),
+ Pot(98, 25, PotItem.FiveArrows, 'GT Mimics 2', obj=RoomObject(0x1FF800, [0xC7, 0xCB, 0xFA]))],
+ 0x6C: [Pot(20, 6, PotItem.Heart, 'GT Quad Pot', obj=RoomObject(0x1FFA8E, [0x2B, 0x33, 0xFA])),
+ Pot(40, 6, PotItem.FiveArrows, 'GT Quad Pot', obj=RoomObject(0x1FFA91, [0x53, 0x33, 0xFA])),
+ Pot(20, 10, PotItem.Bomb, 'GT Quad Pot', obj=RoomObject(0x1FFA94, [0x2B, 0x53, 0xFA])),
+ Pot(40, 10, PotItem.SmallMagic, 'GT Quad Pot', obj=RoomObject(0x1FFA97, [0x53, 0x53, 0xFA]))],
+ 0x6D: [Pot(28, 26, PotItem.Heart, 'GT Gauntlet 5', obj=RoomObject(0x1FFA3C, [0x3B, 0xD3, 0xFA])),
+ Pot(32, 26, PotItem.Heart, 'GT Gauntlet 5', obj=RoomObject(0x1FFA3F, [0x43, 0xD3, 0xFA])),
+ Pot(28, 27, PotItem.SmallMagic, 'GT Gauntlet 5', obj=RoomObject(0x1FFA42, [0x3B, 0xDB, 0xFA])),
+ Pot(32, 27, PotItem.SmallMagic, 'GT Gauntlet 5', obj=RoomObject(0x1FFA45, [0x43, 0xDB, 0xFA]))],
+ 0x73: [Pot(154, 21, PotItem.FiveArrows, 'Desert Circle of Pots', obj=RoomObject(0x1F8933, [0x37, 0xAF, 0xFA])),
+ Pot(158, 21, PotItem.OneRupee, 'Desert Circle of Pots', obj=RoomObject(0x1F8936, [0x3F, 0xAF, 0xFA])),
+ Pot(20, 23, PotItem.Switch, 'Desert Circle of Pots', obj=RoomObject(0x1F8939, [0x2B, 0xBB, 0xFA])),
+ Pot(36, 23, PotItem.FiveRupees, 'Desert Circle of Pots', obj=RoomObject(0x1F894E, [0x4B, 0xBB, 0xFA])),
+ Pot(144, 24, PotItem.Heart, 'Desert Circle of Pots', obj=RoomObject(0x1F893C, [0x23, 0xC7, 0xFA])),
+ Pot(168, 24, PotItem.FiveArrows, 'Desert Circle of Pots', obj=RoomObject(0x1F894B, [0x53, 0xC7, 0xFA])),
+ Pot(20, 26, PotItem.SmallMagic, 'Desert Circle of Pots', obj=RoomObject(0x1F893F, [0x2B, 0xD3, 0xFA])),
+ Pot(36, 26, PotItem.Heart, 'Desert Circle of Pots', obj=RoomObject(0x1F8948, [0x4B, 0xD3, 0xFA])),
+ Pot(154, 27, PotItem.OneRupee, 'Desert Circle of Pots', obj=RoomObject(0x1F8942, [0x37, 0xDF, 0xFA])),
+ Pot(158, 27, PotItem.FiveRupees, 'Desert Circle of Pots', obj=RoomObject(0x1F8945, [0x3F, 0xDF, 0xFA]))],
+ 0x74: [Pot(30, 5, PotItem.SmallMagic, 'Desert Map Room', obj=RoomObject(0x1F8A39, [0x3F, 0x2B, 0xFA])),
+ Pot(62, 5, PotItem.Switch, 'Desert Map Room', obj=RoomObject(0x1F89FD, [0x7F, 0x2B, 0xFA])),
+ Pot(94, 5, PotItem.SmallMagic, 'Desert Map Room', obj=RoomObject(0x1F8A48, [0xBF, 0x2B, 0xFA])),
+ Pot(14, 11, PotItem.Heart, 'Desert Map Room', obj=RoomObject(0x1F8A3C, [0x1F, 0x5B, 0xFA])),
+ Pot(46, 11, PotItem.FiveArrows, 'Desert Map Room', obj=RoomObject(0x1F8A3F, [0x5F, 0x5B, 0xFA])),
+ Pot(78, 11, PotItem.FiveArrows, 'Desert Map Room', obj=RoomObject(0x1F8A42, [0x9F, 0x5B, 0xFA])),
+ Pot(110, 11, PotItem.Heart, 'Desert Map Room', obj=RoomObject(0x1F8A45, [0xDF, 0x5B, 0xFA]))],
+ 0x75: [Pot(148, 22, PotItem.SmallMagic, 'Desert Arrow Pot Corner', obj=RoomObject(0x1F8A89, [0x2B, 0xB7, 0xFA])),
+ Pot(160, 22, PotItem.FiveArrows, 'Desert Arrow Pot Corner', obj=RoomObject(0x1F8A8C, [0x43, 0xB7, 0xFA])),
+ Pot(172, 22, PotItem.Heart, 'Desert Arrow Pot Corner', obj=RoomObject(0x1F8A8F, [0x5B, 0xB7, 0xFA]))],
+ 0x76: [Pot(112, 12, PotItem.Heart, 'Swamp Drain Right', obj=RoomObject(0x1F9DCC, [0xE3, 0x63, 0xFA])),
+ Pot(84, 23, PotItem.Heart, 'Swamp Flooded Spot', obj=RoomObject(0x1F9DF3, [0xAB, 0xBB, 0xFA])),
+ Pot(96, 23, PotItem.Heart, 'Swamp Flooded Spot', obj=RoomObject(0x1F9DF6, [0xC3, 0xBB, 0xFA]))],
+ 0x7B: [Pot(48, 10, PotItem.Nothing, 'GT Conveyor Star Pits', obj=RoomObject(0x1FEF9B, [0x63, 0x53, 0xFA])),
+ Pot(88, 10, PotItem.Nothing, 'GT Conveyor Star Pits', obj=RoomObject(0x1FEFA1, [0xB3, 0x53, 0xFA])),
+ Pot(76, 7, PotItem.Nothing, 'GT Conveyor Star Pits', obj=RoomObject(0x1FEF9E, [0x9B, 0x3B, 0xFA])),
+ Pot(60, 4, PotItem.Heart, 'GT Conveyor Star Pits', obj=RoomObject(0x1FEFAD, [0x7B, 0x23, 0xFA])),
+ Pot(64, 4, PotItem.Key, 'GT Conveyor Star Pits', obj=RoomObject(0x1FEFB0, [0x83, 0x23, 0xFA]))],
+ 0x7C: [Pot(36, 21, PotItem.Nothing, 'GT Falling Bridge', obj=RoomObject(0x1FF0AA, [0x4B, 0xAB, 0xFA])),
+ Pot(24, 11, PotItem.Nothing, 'GT Falling Bridge', obj=RoomObject(0x1FF095, [0x33, 0x5B, 0xFA])),
+ Pot(28, 4, PotItem.Heart, 'GT Falling Bridge', obj=RoomObject(0x1FF08F, [0x3B, 0x23, 0xFA])),
+ Pot(32, 4, PotItem.Heart, 'GT Falling Bridge', obj=RoomObject(0x1FF092, [0x43, 0x23, 0xFA]))],
+ 0x7D: [Pot(44, 12, PotItem.Nothing, 'GT Firesnake Room', obj=RoomObject(0x1FF155, [0x5B, 0x63, 0xFA])),
+ Pot(44, 6, PotItem.Nothing, 'GT Firesnake Room', obj=RoomObject(0x1FF152, [0x5B, 0x33, 0xFA])),
+ Pot(112, 6, PotItem.Heart, 'GT Firesnake Room', obj=RoomObject(0x1FF16A, [0xE3, 0x33, 0xFA])),
+ Pot(108, 20, PotItem.FiveArrows, 'GT Warp Maze - Pot Rail', obj=RoomObject(0x1FF1EB, [0xDB, 0xA3, 0xFA])),
+ Pot(114, 20, PotItem.Bomb, 'GT Petting Zoo', obj=RoomObject(0x1FF1EE, [0xE7, 0xA3, 0xFA])),
+ Pot(76, 28, PotItem.Bomb, 'GT Petting Zoo', obj=RoomObject(0x1FF1F1, [0x9B, 0xE3, 0xFA]))],
+ 0x7E: [Pot(86, 15, PotItem.Heart, 'Ice Tall Hint', obj=RoomObject(0x1FC77E, [0xAF, 0x7B, 0xFA])),
+ Pot(82, 26, PotItem.SmallMagic, 'Ice Tall Hint', obj=RoomObject(0x1FC781, [0xA7, 0xD3, 0xFA])),
+ Pot(100, 26, PotItem.Switch, 'Ice Tall Hint', obj=RoomObject(0x1FC7A2, [0xCB, 0xD3, 0xFA])),
+ Pot(104, 26, PotItem.Nothing, 'Ice Tall Hint', obj=RoomObject(0x1FC7A5, [0xD3, 0xD3, 0xFA]))],
+ 0x80: [Pot(48, 4, PotItem.Heart, 'Hyrule Dungeon Cellblock', obj=RoomObject(0x0AA3CD, [0x63, 0x23, 0xFA])),
+ Pot(52, 4, PotItem.Heart, 'Hyrule Dungeon Cellblock', obj=RoomObject(0x0AA3D3, [0x6B, 0x23, 0xFA])),
+ Pot(56, 4, PotItem.Heart, 'Hyrule Dungeon Cellblock', obj=RoomObject(0x0AA3D6, [0x73, 0x23, 0xFA]))],
+ 0x82: [Pot(50, 0x5, PotItem.Nothing, 'Hyrule Dungeon South Abyss', PotFlags.LowerRegion, obj=RoomObject(0x0AA0D5, [0x67, 0x2B, 0xFA])),
+ Pot(50, 0xA, PotItem.Nothing, 'Hyrule Dungeon South Abyss', PotFlags.LowerRegion, obj=RoomObject(0x0AA0D8, [0x67, 0x53, 0xFA])),
+ Pot(76, 0x12, PotItem.Heart, 'Hyrule Dungeon South Abyss', PotFlags.LowerRegion, obj=RoomObject(0x0AA0D2, [0x9B, 0x93, 0xFA]))],
+ 0x83: [Pot(76, 4, PotItem.FiveArrows, 'Desert West Wing', obj=RoomObject(0x1F8B54, [0x9B, 0x23, 0xFA])),
+ Pot(80, 4, PotItem.OneRupee, 'Desert West Wing', obj=RoomObject(0x1F8B57, [0xA3, 0x23, 0xFA])),
+ Pot(76, 28, PotItem.FiveRupees, 'Desert West Wing', obj=RoomObject(0x1F8B5A, [0x9B, 0xE3, 0xFA])),
+ Pot(80, 28, PotItem.FiveArrows, 'Desert West Wing', obj=RoomObject(0x1F8B5D, [0xA3, 0xE3, 0xFA]))],
+ 0x84: [Pot(64, 17, PotItem.Nothing, 'Desert Main Lobby', obj=RoomObject(0x1F8C90, [0x83, 0x8B, 0xFA])),
+ Pot(60, 17, PotItem.Nothing, 'Desert Main Lobby', obj=RoomObject(0x1F8C8D, [0x7B, 0x8B, 0xFA])),
+ Pot(80, 14, PotItem.Nothing, 'Desert Main Lobby', obj=RoomObject(0x1F8C93, [0xA3, 0x73, 0xFA])),
+ Pot(44, 14, PotItem.Nothing, 'Desert Main Lobby', obj=RoomObject(0x1F8C96, [0x5B, 0x73, 0xFA])),
+ Pot(100, 6, PotItem.Nothing, 'Desert Main Lobby', obj=RoomObject(0x1F8C87, [0xCB, 0x33, 0xFA])),
+ Pot(24, 6, PotItem.Nothing, 'Desert Main Lobby', obj=RoomObject(0x1F8C81, [0x33, 0x33, 0xFA])),
+ Pot(24, 7, PotItem.FiveArrows, 'Desert Main Lobby', obj=RoomObject(0x1F8C84, [0x33, 0x3B, 0xFA])),
+ Pot(100, 7, PotItem.FiveArrows, 'Desert Main Lobby', obj=RoomObject(0x1F8C8A, [0xCB, 0x3B, 0xFA]))],
+ 0x85: [Pot(44, 28, PotItem.Heart, 'Desert East Wing', obj=RoomObject(0x1F8D59, [0x5B, 0xE3, 0xFA])),
+ Pot(48, 28, PotItem.FiveArrows, 'Desert East Wing', obj=RoomObject(0x1F8D5C, [0x63, 0xE3, 0xFA]))],
+ 0x87: [Pot(12, 11, PotItem.Nothing, 'Hera Tile Room', obj=RoomObject(0x1FD12A, [0x1B, 0x5B, 0xFA])),
+ Pot(16, 12, PotItem.Nothing, 'Hera Tile Room', obj=RoomObject(0x1FD130, [0x23, 0x63, 0xFA])),
+ Pot(40, 12, PotItem.Nothing, 'Hera Tile Room', obj=RoomObject(0x1FD139, [0x53, 0x63, 0xFA])),
+ Pot(32, 12, PotItem.Nothing, 'Hera Tile Room', obj=RoomObject(0x1FD136, [0x43, 0x63, 0xFA])),
+ Pot(24, 12, PotItem.Nothing, 'Hera Tile Room', obj=RoomObject(0x1FD133, [0x33, 0x63, 0xFA])),
+ Pot(16, 11, PotItem.Nothing, 'Hera Tile Room', obj=RoomObject(0x1FD12D, [0x23, 0x5B, 0xFA])),
+ Pot(76, 20, PotItem.SmallMagic, 'Hera Torches', obj=RoomObject(0x1FD18D, [0x9B, 0xA3, 0xFA])),
+ Pot(112, 20, PotItem.BigMagic, 'Hera Torches', obj=RoomObject(0x1FD190, [0xE3, 0xA3, 0xFA]))],
+ 0x8B: [Pot(76, 12, PotItem.Nothing, 'GT Conveyor Cross', obj=RoomObject(0x1FF2F7, [0x9B, 0x63, 0xFA])),
+ Pot(112, 12, PotItem.Key, 'GT Conveyor Cross', obj=RoomObject(0x1FF2FA, [0xE3, 0x63, 0xFA])),
+ Pot(32, 23, PotItem.Nothing, 'GT Hookshot South Platform', obj=RoomObject(0x1FF2C7, [0x43, 0xBB, 0xFA])),
+ Pot(28, 23, PotItem.Nothing, 'GT Hookshot South Platform', obj=RoomObject(0x1FF2C4, [0x3B, 0xBB, 0xFA])),
+ Pot(32, 9, PotItem.SmallMagic, 'GT Hookshot Mid Platform', obj=RoomObject(0x1FF2C1, [0x43, 0x4B, 0xFA])),
+ Pot(76, 20, PotItem.Nothing, 'GT Map Room', obj=RoomObject(0x1FF309, [0x9B, 0xA3, 0xFA])),
+ Pot(76, 28, PotItem.Heart, 'GT Map Room', obj=RoomObject(0x1FF30C, [0x9B, 0xE3, 0xFA]))],
+ 0x8C: [Pot(76, 12, PotItem.Switch, 'GT Hope Room', obj=RoomObject(0x1FF377, [0x9B, 0x63, 0xFA])),
+ Pot(112, 12, PotItem.SmallMagic, 'GT Hope Room', obj=RoomObject(0x1FF37A, [0xE3, 0x63, 0xFA])),
+ Pot(76, 20, PotItem.Bomb, "GT Bob's Room", obj=RoomObject(0x1FF3B9, [0x9B, 0xA3, 0xFA])),
+ Pot(92, 20, PotItem.Bomb, "GT Bob's Room", obj=RoomObject(0x1FF3BF, [0xBB, 0xA3, 0xFA])),
+ Pot(100, 21, PotItem.FiveArrows, "GT Bob's Room", obj=RoomObject(0x1FF3C2, [0xCB, 0xAB, 0xFA])),
+ Pot(104, 26, PotItem.Bomb, "GT Bob's Room", obj=RoomObject(0x1FF3E0, [0xD3, 0xD3, 0xFA])),
+ Pot(88, 27, PotItem.Bomb, "GT Bob's Room", obj=RoomObject(0x1FF3BC, [0xB3, 0xDB, 0xFA]))],
+ 0x8D: [Pot(204, 11, PotItem.Nothing, 'GT Speed Torch Upper', obj=RoomObject(0x1FF492, [0x9B, 0x5F, 0xFA])),
+ Pot(204, 14, PotItem.BigMagic, 'GT Speed Torch Upper', obj=RoomObject(0x1FF495, [0x9B, 0x77, 0xFA])),
+ Pot(28, 23, PotItem.Heart, 'GT Pots n Blocks', obj=RoomObject(0x1FF477, [0x3B, 0xBB, 0xFA])),
+ Pot(36, 23, PotItem.Heart, 'GT Pots n Blocks', obj=RoomObject(0x1FF47D, [0x4B, 0xBB, 0xFA])),
+ Pot(32, 24, PotItem.BigMagic, 'GT Pots n Blocks', obj=RoomObject(0x1FF47A, [0x43, 0xC3, 0xFA]))],
+ 0x8E: [Pot(80, 5, PotItem.FiveArrows, 'Ice Lonely Freezor', obj=RoomObject(0x1FC835, [0xA3, 0x2B, 0xFA])),
+ Pot(80, 6, PotItem.Nothing, 'Ice Lonely Freezor', obj=RoomObject(0x1FC838, [0xA3, 0x33, 0xFA]))],
+ 0x91: [Pot(84, 4, PotItem.Heart, 'Mire Falling Foes', obj=RoomObject(0x1FB9B0, [0xAB, 0x23, 0xFA])),
+ Pot(104, 4, PotItem.SmallMagic, 'Mire Falling Foes', obj=RoomObject(0x1FB9B3, [0xD3, 0x23, 0xFA]))],
+ 0x92: [Pot(86, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy', obj=RoomObject(0x1FB966, [0xAF, 0xBB, 0xFA])),
+ Pot(92, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy', obj=RoomObject(0x1FB969, [0xBB, 0xBB, 0xFA])),
+ Pot(98, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy', obj=RoomObject(0x1FB96C, [0xC7, 0xBB, 0xFA])),
+ Pot(104, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy', obj=RoomObject(0x1FB96F, [0xD3, 0xBB, 0xFA]))],
+ 0x93: [Pot(28, 7, PotItem.Switch, 'Mire Dark Shooters', obj=RoomObject(0x1FB85D, [0x3B, 0x3B, 0xFA])),
+ Pot(0x9C, 0x17, PotItem.Nothing, 'Mire Block X', PotFlags.Block),
+ Pot(96, 7, PotItem.Heart, 'Mire Dark Shooters', PotFlags.NoSwitch, obj=RoomObject(0x1FB860, [0xC3, 0x3B, 0xFA]))],
+ 0x96: [Pot(14, 18, PotItem.Nothing, 'GT Torch Cross', obj=RoomObject(0x1FFC69, [0x1F, 0x93, 0xFA])),
+ Pot(32, 5, PotItem.Nothing, 'GT Torch Cross', obj=RoomObject(0x1FFC5D, [0x43, 0x2B, 0xFA])),
+ Pot(46, 11, PotItem.Nothing, 'GT Torch Cross', obj=RoomObject(0x1FFC60, [0x5F, 0x5B, 0xFA])),
+ Pot(32, 17, PotItem.SmallMagic, 'GT Torch Cross', obj=RoomObject(0x1FFC63, [0x43, 0x8B, 0xFA])),
+ Pot(32, 24, PotItem.SmallMagic, 'GT Torch Cross', obj=RoomObject(0x1FFC6F, [0x43, 0xC3, 0xFA])),
+ Pot(14, 24, PotItem.Nothing, 'GT Torch Cross', obj=RoomObject(0x1FFC6C, [0x1F, 0xC3, 0xFA])),
+ Pot(76, 21, PotItem.Heart, 'GT Staredown', obj=RoomObject(0x1FFC0F, [0x9B, 0xAB, 0xFA])),
+ Pot(112, 21, PotItem.BigMagic, 'GT Staredown', obj=RoomObject(0x1FFC12, [0xE3, 0xAB, 0xFA]))],
+ 0x99: [Pot(40, 20, PotItem.SmallMagic, 'Eastern Darkness', obj=RoomObject(0x0A96F4, [0x53, 0xA3, 0xFA])),
+ Pot(84, 20, PotItem.Heart, 'Eastern Darkness', obj=RoomObject(0x0A96F7, [0xAB, 0xA3, 0xFA]))],
+ 0x9B: [Pot(48, 4, PotItem.SmallMagic, 'GT Double Switch Pot Corners', obj=RoomObject(0x1FF509, [0x63, 0x23, 0xFA])),
+ Pot(48, 12, PotItem.Key, 'GT Double Switch Pot Corners', obj=RoomObject(0x1FF50C, [0x63, 0x63, 0xFA])),
+ Pot(28, 24, PotItem.Nothing, 'GT Warp Maze - Mid Section', obj=RoomObject(0x1FF53C, [0x3B, 0xC3, 0xFA])),
+ Pot(32, 24, PotItem.Nothing, 'GT Warp Maze - Mid Section', obj=RoomObject(0x1FF53F, [0x43, 0xC3, 0xFA]))],
+ 0x9C: [Pot(56, 8, PotItem.SmallMagic, 'GT Invisible Catwalk', obj=RoomObject(0x1FF693, [0x73, 0x43, 0xFA])),
+ Pot(56, 9, PotItem.FiveArrows, 'GT Invisible Catwalk', obj=RoomObject(0x1FF696, [0x73, 0x4B, 0xFA]))],
+ 0x9D: [Pot(76, 4, PotItem.Bomb, 'GT Crystal Conveyor Left', obj=RoomObject(0x1FF6ED, [0x9B, 0x23, 0xFA])),
+ Pot(84, 4, PotItem.SmallMagic, 'GT Crystal Conveyor Left', obj=RoomObject(0x1FF6F0, [0xAB, 0x23, 0xFA])),
+ Pot(32, 7, PotItem.Nothing, 'GT Compass Room', obj=RoomObject(0x1FF6EA, [0x43, 0x3B, 0xFA])),
+ Pot(40, 9, PotItem.Nothing, 'GT Compass Room', obj=RoomObject(0x1FF6E7, [0x53, 0x4B, 0xFA]))],
+ 0x9F: [Pot(138, 20, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC904, [0x17, 0xA7, 0xFA])),
+ Pot(138, 19, PotItem.Heart, 'Ice Many Pots', obj=RoomObject(0x1FC901, [0x17, 0x9F, 0xFA])),
+ Pot(178, 19, PotItem.Heart, 'Ice Many Pots', obj=RoomObject(0x1FC913, [0x67, 0x9F, 0xFA])),
+ Pot(40, 21, PotItem.Switch, 'Ice Many Pots', obj=RoomObject(0x1FC928, [0x53, 0xAB, 0xFA])),
+ Pot(138, 21, PotItem.Key, 'Ice Many Pots', obj=RoomObject(0x1FC907, [0x17, 0xAF, 0xFA])),
+ Pot(20, 27, PotItem.Heart, 'Ice Many Pots', obj=RoomObject(0x1FC92B, [0x2B, 0xDB, 0xFA])),
+ Pot(138, 27, PotItem.Heart, 'Ice Many Pots', obj=RoomObject(0x1FC90D, [0x17, 0xDF, 0xFA])),
+ Pot(178, 28, PotItem.Heart, 'Ice Many Pots', obj=RoomObject(0x1FC922, [0x67, 0xE7, 0xFA])),
+ Pot(178, 21, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC919, [0x67, 0xAF, 0xFA])),
+ Pot(178, 20, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC916, [0x67, 0xA7, 0xFA])),
+ Pot(40, 27, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC92E, [0x53, 0xDB, 0xFA])),
+ Pot(178, 27, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC91F, [0x67, 0xDF, 0xFA])),
+ Pot(178, 26, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC91C, [0x67, 0xD7, 0xFA])),
+ Pot(138, 28, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC910, [0x17, 0xE7, 0xFA])),
+ Pot(138, 26, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC90A, [0x17, 0xD7, 0xFA])),
+ Pot(20, 21, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC925, [0x2B, 0xAB, 0xFA]))],
+ 0xA1: [Pot(150, 6, PotItem.Key, 'Mire Fishbone', obj=RoomObject(0x1FB7F2, [0x2F, 0x37, 0xFA])),
+ Pot(100, 11, PotItem.SmallMagic, 'Mire Fishbone', obj=RoomObject(0x1FB80D, [0xCB, 0x5B, 0xFA])),
+ Pot(104, 12, PotItem.Heart, 'Mire Fishbone', obj=RoomObject(0x1FB810, [0xD3, 0x63, 0xFA])),
+ Pot(108, 13, PotItem.SmallMagic, 'Mire Fishbone', obj=RoomObject(0x1FB813, [0xDB, 0x6B, 0xFA])),
+ Pot(112, 14, PotItem.Heart, 'Mire Fishbone', obj=RoomObject(0x1FB816, [0xE3, 0x73, 0xFA])),
+ Pot(96, 27, PotItem.Nothing, 'Mire South Fish', obj=RoomObject(0x1FB804, [0xC3, 0xDB, 0xFA])),
+ Pot(92, 21, PotItem.Nothing, 'Mire South Fish', obj=RoomObject(0x1FB7FB, [0xBB, 0xAB, 0xFA])),
+ Pot(96, 23, PotItem.Heart, 'Mire South Fish', obj=RoomObject(0x1FB7FE, [0xC3, 0xBB, 0xFA])),
+ Pot(92, 25, PotItem.Nothing, 'Mire South Fish', obj=RoomObject(0x1FB801, [0xBB, 0xCB, 0xFA])),
+ Pot(76, 28, PotItem.Nothing, 'Mire South Fish', obj=RoomObject(0x1FB807, [0x9B, 0xE3, 0xFA])),
+ Pot(112, 28, PotItem.Nothing, 'Mire South Fish', obj=RoomObject(0x1FB80A, [0xE3, 0xE3, 0xFA]))],
+ 0xA2: [Pot(12, 28, PotItem.BigMagic, 'Mire Left Bridge', obj=RoomObject(0x1FB6B1, [0x1B, 0xE3, 0xFA]))],
+ 0xA8: [Pot(138, 28, PotItem.Nothing, 'Eastern Stalfos Spawn', obj=RoomObject(0x0A97BB, [0x17, 0xE7, 0xFA])),
+ Pot(178, 28, PotItem.Nothing, 'Eastern Stalfos Spawn', obj=RoomObject(0x0A97BE, [0x67, 0xE7, 0xFA])),
+ Pot(178, 19, PotItem.Nothing, 'Eastern Stalfos Spawn', obj=RoomObject(0x0A97B8, [0x67, 0x9F, 0xFA])),
+ Pot(138, 19, PotItem.Heart, 'Eastern Stalfos Spawn', obj=RoomObject(0x0A97B5, [0x17, 0x9F, 0xFA])),
+ Pot(30, 24, PotItem.OneRupee, 'Eastern Stalfos Spawn', obj=RoomObject(0x0A97C1, [0x3F, 0xC3, 0xFA]))],
+ 0xA9: [Pot(144, 0xB, PotItem.FiveArrows, 'Eastern Courtyard', PotFlags.LowerRegion, obj=RoomObject(0x0A9983, [0x23, 0x5F, 0xFA])),
+ Pot(236, 0xB, PotItem.FiveArrows, 'Eastern Courtyard', PotFlags.LowerRegion, obj=RoomObject(0x0A9989, [0xDB, 0x5F, 0xFA])),
+ Pot(144, 0xC, PotItem.FiveArrows, 'Eastern Courtyard', PotFlags.LowerRegion, obj=RoomObject(0x0A9986, [0x23, 0x67, 0xFA])),
+ Pot(236, 0xC, PotItem.Heart, 'Eastern Courtyard', PotFlags.LowerRegion, obj=RoomObject(0x0A998C, [0xDB, 0x67, 0xFA])),
+ Pot(12, 19, PotItem.Nothing, 'Eastern Courtyard Ledge', obj=RoomObject(0x0A994B, [0x1B, 0x9B, 0xFA])),
+ Pot(112, 19, PotItem.Nothing, 'Eastern Courtyard Ledge', obj=RoomObject(0x0A993C, [0xE3, 0x9B, 0xFA])),
+ Pot(16, 20, PotItem.Heart, 'Eastern Courtyard Ledge', obj=RoomObject(0x0A994E, [0x23, 0xA3, 0xFA])),
+ Pot(108, 20, PotItem.Heart, 'Eastern Courtyard Ledge', obj=RoomObject(0x0A9936, [0xDB, 0xA3, 0xFA]))],
+ 0xAA: [Pot(212, 10, PotItem.Nothing, 'Eastern Pot Switch', obj=RoomObject(0x0A9AA5, [0xAB, 0x57, 0xFA])),
+ Pot(232, 10, PotItem.Nothing, 'Eastern Pot Switch', obj=RoomObject(0x0A9AA8, [0xD3, 0x57, 0xFA])),
+ Pot(232, 5, PotItem.Nothing, 'Eastern Pot Switch', obj=RoomObject(0x0A9AA2, [0xD3, 0x2F, 0xFA])),
+ Pot(212, 5, PotItem.Heart, 'Eastern Pot Switch', obj=RoomObject(0x0A9A9F, [0xAB, 0x2F, 0xFA])),
+ Pot(94, 8, PotItem.Switch, 'Eastern Pot Switch', obj=RoomObject(0x0A9AAB, [0xBF, 0x43, 0xFA])),
+ Pot(108, 0x17, PotItem.Heart, 'Eastern Map Balcony', PotFlags.LowerRegion, obj=RoomObject(0x0A9AF5, [0xDB, 0xBB, 0xFA])),
+ Pot(108, 0x18, PotItem.Heart, 'Eastern Map Balcony', PotFlags.LowerRegion, obj=RoomObject(0x0A9AF8, [0xDB, 0xC3, 0xFA])),
+ Pot(108, 0x19, PotItem.Heart, 'Eastern Map Balcony', PotFlags.LowerRegion, obj=RoomObject(0x0A9AFB, [0xDB, 0xCB, 0xFA]))],
+ 0xAB: [Pot(20, 24, PotItem.Key, 'Thieves Spike Switch', obj=RoomObject(0x1FD99D, [0x2B, 0xC3, 0xFA]))],
+ 0xAE: [Pot(76, 12, PotItem.Switch, 'Iced T', obj=RoomObject(0x1FC95D, [0x9B, 0x63, 0xFA]))],
+ 0xB0: [Pot(20, 27, PotItem.Nothing, 'Tower Circle of Pots', obj=RoomObject(0x1F8F2C, [0x2B, 0xDB, 0xFA])),
+ Pot(24, 24, PotItem.Nothing, 'Tower Circle of Pots', obj=RoomObject(0x1F8F26, [0x33, 0xC3, 0xFA])),
+ Pot(44, 25, PotItem.Nothing, 'Tower Circle of Pots', obj=RoomObject(0x1F8F38, [0x5B, 0xCB, 0xFA])),
+ Pot(20, 21, PotItem.Bomb, 'Tower Circle of Pots', obj=RoomObject(0x1F8F14, [0x2B, 0xAB, 0xFA])),
+ Pot(28, 21, PotItem.OneRupee, 'Tower Circle of Pots', obj=RoomObject(0x1F8F17, [0x3B, 0xAB, 0xFA])),
+ Pot(32, 21, PotItem.FiveRupees, 'Tower Circle of Pots', obj=RoomObject(0x1F8F1A, [0x43, 0xAB, 0xFA])),
+ Pot(40, 21, PotItem.FiveArrows, 'Tower Circle of Pots', obj=RoomObject(0x1F8F1D, [0x53, 0xAB, 0xFA])),
+ Pot(16, 23, PotItem.FiveRupees, 'Tower Circle of Pots', obj=RoomObject(0x1F8F20, [0x23, 0xBB, 0xFA])),
+ Pot(44, 23, PotItem.OneRupee, 'Tower Circle of Pots', obj=RoomObject(0x1F8F3B, [0x5B, 0xBB, 0xFA])),
+ Pot(36, 24, PotItem.Heart, 'Tower Circle of Pots', obj=RoomObject(0x1F8F29, [0x4B, 0xC3, 0xFA])),
+ Pot(16, 25, PotItem.Heart, 'Tower Circle of Pots', obj=RoomObject(0x1F8F23, [0x23, 0xCB, 0xFA])),
+ Pot(28, 27, PotItem.FiveArrows, 'Tower Circle of Pots', obj=RoomObject(0x1F8F2F, [0x3B, 0xDB, 0xFA])),
+ Pot(40, 27, PotItem.Bomb, 'Tower Circle of Pots', obj=RoomObject(0x1F8F35, [0x53, 0xDB, 0xFA])),
+ Pot(32, 27, PotItem.Nothing, 'Tower Circle of Pots', obj=RoomObject(0x1F8F32, [0x43, 0xDB, 0xFA]))],
+ 0xB1: [Pot(76, 4, PotItem.Heart, 'Mire Spike Barrier', obj=RoomObject(0x1FB35A, [0x9B, 0x23, 0xFA])),
+ Pot(112, 4, PotItem.OneRupee, 'Mire Spike Barrier', obj=RoomObject(0x1FB35D, [0xE3, 0x23, 0xFA]))],
+ 0xB2: [Pot(48, 0x8, PotItem.OneRupee, 'Mire BK Door Room', PotFlags.LowerRegion, obj=RoomObject(0x1FB467, [0x63, 0x43, 0xFA])),
+ Pot(76, 0x8, PotItem.OneRupee, 'Mire BK Door Room', PotFlags.LowerRegion, obj=RoomObject(0x1FB470, [0x9B, 0x43, 0xFA])),
+ Pot(48, 0x9, PotItem.Nothing, 'Mire BK Door Room', PotFlags.LowerRegion, obj=RoomObject(0x1FB46A, [0x63, 0x4B, 0xFA])),
+ Pot(76, 0x9, PotItem.Heart, 'Mire BK Door Room', PotFlags.LowerRegion, obj=RoomObject(0x1FB473, [0x9B, 0x4B, 0xFA])),
+ Pot(48, 0xA, PotItem.Nothing, 'Mire BK Door Room', PotFlags.LowerRegion, obj=RoomObject(0x1FB46D, [0x63, 0x53, 0xFA])),
+ Pot(76, 0xA, PotItem.Nothing, 'Mire BK Door Room', PotFlags.LowerRegion, obj=RoomObject(0x1FB476, [0x9B, 0x53, 0xFA]))],
+ 0xB3: [Pot(12, 20, PotItem.Key, 'Mire Spikes', obj=RoomObject(0x1FB5A2, [0x1B, 0xA3, 0xFA])),
+ Pot(48, 20, PotItem.SmallMagic, 'Mire Spikes', obj=RoomObject(0x1FB5A5, [0x63, 0xA3, 0xFA])),
+ Pot(48, 28, PotItem.Switch, 'Mire Spikes', obj=RoomObject(0x1FB5C0, [0x63, 0xE3, 0xFA]))],
+ 0xB4: [Pot(44, 28, PotItem.BigMagic, 'TR Final Abyss Balcony', obj=RoomObject(0x1FE7B6, [0x5B, 0xE3, 0xFA])),
+ Pot(48, 28, PotItem.Heart, 'TR Final Abyss Balcony', obj=RoomObject(0x1FE7B9, [0x63, 0xE3, 0xFA]))],
+ 0xB5: [Pot(112, 4, PotItem.FiveRupees, 'TR Dark Ride Ledges', obj=RoomObject(0x1FEA78, [0xE3, 0x23, 0xFA])),
+ Pot(112, 15, PotItem.Heart, 'TR Dark Ride Ledges', obj=RoomObject(0x1FEAD5, [0xE3, 0x7B, 0xFA])),
+ Pot(76, 16, PotItem.Switch, 'TR Dark Ride Ledges', obj=RoomObject(0x1FEA93, [0x9B, 0x83, 0xFA])),
+ Pot(112, 16, PotItem.BigMagic, 'TR Dark Ride Ledges', obj=RoomObject(0x1FEAD8, [0xE3, 0x83, 0xFA])),
+ Pot(112, 17, PotItem.Heart, 'TR Dark Ride Ledges', obj=RoomObject(0x1FEADB, [0xE3, 0x8B, 0xFA])),
+ Pot(112, 28, PotItem.Bomb, 'TR Dark Ride Ledges', obj=RoomObject(0x1FEAF3, [0xE3, 0xE3, 0xFA]))],
+ 0xB6: [Pot(94, 9, PotItem.BigMagic, 'TR Refill', obj=RoomObject(0x1FDD29, [0xBF, 0x4B, 0xFA]))],
+ 0xB7: [Pot(30, 5, PotItem.SmallMagic, 'TR Roller Room', obj=RoomObject(0x1FDD76, [0x3F, 0x2B, 0xFA]))],
+ 0xB8: [Pot(96, 13, PotItem.Switch, 'Eastern Big Key', obj=RoomObject(0x0A9B66, [0xC3, 0x6B, 0xFA])),
+ Pot(88, 16, PotItem.Heart, 'Eastern Big Key', obj=RoomObject(0x0A9B60, [0xB3, 0x83, 0xFA])),
+ Pot(104, 16, PotItem.Heart, 'Eastern Big Key', obj=RoomObject(0x0A9B63, [0xD3, 0x83, 0xFA]))],
+ 0xB9: [Pot(92, 18, PotItem.OneRupee, 'Eastern Cannonball', obj=RoomObject(0x0A9C82, [0xBB, 0x93, 0xFA])),
+ Pot(96, 18, PotItem.FiveRupees, 'Eastern Cannonball', obj=RoomObject(0x0A9C85, [0xC3, 0x93, 0xFA])),
+ Pot(104, 18, PotItem.FiveRupees, 'Eastern Cannonball', obj=RoomObject(0x0A9C88, [0xD3, 0x93, 0xFA])),
+ Pot(108, 18, PotItem.OneRupee, 'Eastern Cannonball', obj=RoomObject(0x0A9C8B, [0xDB, 0x93, 0xFA]))],
+ 0xBA: [Pot(100, 8, PotItem.Nothing, 'Eastern Dark Pots', obj=RoomObject(0x0A9D3F, [0xCB, 0x43, 0xFA])),
+ Pot(88, 8, PotItem.Nothing, 'Eastern Dark Pots', obj=RoomObject(0x0A9D36, [0xB3, 0x43, 0xFA])),
+ Pot(94, 4, PotItem.OneRupee, 'Eastern Dark Pots', obj=RoomObject(0x0A9D39, [0xBF, 0x23, 0xFA])),
+ Pot(76, 6, PotItem.Heart, 'Eastern Dark Pots', obj=RoomObject(0x0A9D30, [0x9B, 0x33, 0xFA])),
+ Pot(112, 6, PotItem.Key, 'Eastern Dark Pots', obj=RoomObject(0x0A9D42, [0xE3, 0x33, 0xFA])),
+ Pot(76, 10, PotItem.Heart, 'Eastern Dark Pots', obj=RoomObject(0x0A9D33, [0x9B, 0x53, 0xFA])),
+ Pot(112, 10, PotItem.SmallMagic, 'Eastern Dark Pots', obj=RoomObject(0x0A9D45, [0xE3, 0x53, 0xFA])),
+ Pot(94, 12, PotItem.OneRupee, 'Eastern Dark Pots', obj=RoomObject(0x0A9D3C, [0xBF, 0x63, 0xFA]))],
+ 0xBC: [Pot(86, 4, PotItem.Heart, 'Thieves Hallway', obj=RoomObject(0x1FD941, [0xAF, 0x23, 0xFA])),
+ Pot(102, 4, PotItem.Key, 'Thieves Hallway', obj=RoomObject(0x1FD944, [0xCF, 0x23, 0xFA])),
+ Pot(138, 3, PotItem.Bomb, 'Thieves Conveyor Maze', obj=RoomObject(0x1FD8DB, [0x17, 0x1F, 0xFA])),
+ Pot(178, 3, PotItem.Switch, 'Thieves Conveyor Maze', obj=RoomObject(0x1FD8DE, [0x67, 0x1F, 0xFA])),
+ Pot(138, 12, PotItem.Heart, 'Thieves Conveyor Maze', obj=RoomObject(0x1FD8E1, [0x17, 0x67, 0xFA])),
+ Pot(178, 12, PotItem.Bomb, 'Thieves Conveyor Maze', obj=RoomObject(0x1FD8F3, [0x67, 0x67, 0xFA])),
+ Pot(12, 20, PotItem.Nothing, 'Thieves Pot Alcove Mid', obj=RoomObject(0x1FD8FF, [0x1B, 0xA3, 0xFA])),
+ Pot(48, 20, PotItem.Bomb, 'Thieves Pot Alcove Mid', obj=RoomObject(0x1FD902, [0x63, 0xA3, 0xFA])),
+ Pot(12, 28, PotItem.Bomb, 'Thieves Pot Alcove Mid', obj=RoomObject(0x1FD905, [0x1B, 0xE3, 0xFA])),
+ Pot(48, 28, PotItem.Bomb, 'Thieves Pot Alcove Mid', obj=RoomObject(0x1FD908, [0x63, 0xE3, 0xFA])),
+ Pot(28, 21, PotItem.FiveRupees, 'Thieves Pot Alcove Top', obj=RoomObject(0x1FD914, [0x3B, 0xAB, 0xFA])),
+ Pot(32, 21, PotItem.FiveRupees, 'Thieves Pot Alcove Top', obj=RoomObject(0x1FD917, [0x43, 0xAB, 0xFA])),
+ Pot(28, 27, PotItem.FiveRupees, 'Thieves Pot Alcove Bottom', obj=RoomObject(0x1FD923, [0x3B, 0xDB, 0xFA])),
+ Pot(32, 27, PotItem.FiveRupees, 'Thieves Pot Alcove Bottom', obj=RoomObject(0x1FD926, [0x43, 0xDB, 0xFA]))],
+ 0xBE: [Pot(92, 25, PotItem.Switch, 'Ice Switch Room', obj=RoomObject(0x1FC9E9, [0xBB, 0xCB, 0xFA]))],
+ 0xBF: [Pot(40, 20, PotItem.FiveArrows, 'Ice Refill', obj=RoomObject(0x1FCA56, [0x53, 0xA3, 0xFA])),
+ Pot(44, 20, PotItem.Heart, 'Ice Refill', obj=RoomObject(0x1FCA59, [0x5B, 0xA3, 0xFA])),
+ Pot(48, 20, PotItem.Bomb, 'Ice Refill', obj=RoomObject(0x1FCA5C, [0x63, 0xA3, 0xFA])),
+ Pot(40, 28, PotItem.SmallMagic, 'Ice Refill', obj=RoomObject(0x1FCA5F, [0x53, 0xE3, 0xFA])),
+ Pot(44, 28, PotItem.SmallMagic, 'Ice Refill', obj=RoomObject(0x1FCA62, [0x5B, 0xE3, 0xFA])),
+ Pot(48, 28, PotItem.SmallMagic, 'Ice Refill', obj=RoomObject(0x1FCA65, [0x63, 0xE3, 0xFA]))],
+ 0xC0: [Pot(48, 10, PotItem.Bomb, 'Tower Dark Pits', obj=RoomObject(0x1F8FE1, [0x63, 0x53, 0xFA])),
+ Pot(12, 14, PotItem.FiveRupees, 'Tower Dark Pits', obj=RoomObject(0x1F8FE7, [0x1B, 0x73, 0xFA])),
+ Pot(12, 26, PotItem.Heart, 'Tower Dark Pits', obj=RoomObject(0x1F8FF0, [0x1B, 0xD3, 0xFA])),
+ Pot(28, 27, PotItem.OneRupee, 'Tower Dark Pits', obj=RoomObject(0x1F8FF3, [0x3B, 0xDB, 0xFA]))],
+ 0xC2: [Pot(180, 7, PotItem.Switch, 'Mire Hub Switch', obj=RoomObject(0x1FB0C4, [0x6B, 0x3F, 0xFA])),
+ Pot(100, 0xE, PotItem.SmallMagic, 'Mire Hub Right', PotFlags.LowerRegion, RoomObject(0x1FB071, [0xCB, 0x73, 0xFA])),
+ Pot(68, 0x10, PotItem.OneRupee, 'Mire Hub', PotFlags.LowerRegion, RoomObject(0x1FB086, [0x8B, 0x83, 0xFA])),
+ Pot(64, 0x14, PotItem.FiveArrows, 'Mire Hub', PotFlags.LowerRegion, RoomObject(0x1FB089, [0x83, 0xA3, 0xFA]))],
+ 0xC4: [Pot(84, 9, PotItem.Bomb, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC06, [0xAB, 0x4B, 0xFA])),
+ Pot(24, 14, PotItem.Heart, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC09, [0x33, 0x73, 0xFA])),
+ Pot(56, 17, PotItem.FiveRupees, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC0C, [0x73, 0x8B, 0xFA])),
+ Pot(84, 17, PotItem.Bomb, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC0F, [0xAB, 0x8B, 0xFA])),
+ Pot(12, 21, PotItem.FiveArrows, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC15, [0x1B, 0xAB, 0xFA])),
+ Pot(76, 23, PotItem.OneRupee, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC12, [0x9B, 0xBB, 0xFA])),
+ Pot(48, 25, PotItem.SmallMagic, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC1B, [0x63, 0xCB, 0xFA])),
+ Pot(12, 26, PotItem.Heart, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC18, [0x1B, 0xD3, 0xFA]))],
+ 0xC6: [Pot(12, 7, PotItem.BigMagic, 'TR Hub Ledges', obj=RoomObject(0x1FDF50, [0x1B, 0x3B, 0xFA])),
+ Pot(12, 25, PotItem.Heart, 'TR Hub Ledges', obj=RoomObject(0x1FDF53, [0x1B, 0xCB, 0xFA]))],
+ 0xC7: [Pot(12, 10, PotItem.Heart, 'TR Torches', obj=RoomObject(0x1FE080, [0x1B, 0x53, 0xFA])),
+ Pot(12, 11, PotItem.BigMagic, 'TR Torches', obj=RoomObject(0x1FE083, [0x1B, 0x5B, 0xFA])),
+ Pot(12, 22, PotItem.SmallMagic, 'TR Torches Ledge', obj=RoomObject(0x1FE098, [0x1B, 0xB3, 0xFA])),
+ Pot(12, 28, PotItem.FiveArrows, 'TR Torches Ledge', obj=RoomObject(0x1FE095, [0x1B, 0xE3, 0xFA]))],
+ 0xC9: [Pot(30, 22, PotItem.OneRupee, 'Eastern Lobby', obj=RoomObject(0x0A9E30, [0x3F, 0xB3, 0xFA])),
+ Pot(94, 22, PotItem.OneRupee, 'Eastern Lobby', obj=RoomObject(0x0A9E36, [0xBF, 0xB3, 0xFA])),
+ Pot(60, 22, PotItem.Switch, 'Eastern Lobby', obj=RoomObject(0x0A9E33, [0x7B, 0xB3, 0xFA]))],
+ 0xCB: [Pot(80, 4, PotItem.Nothing, 'Thieves Ambush', obj=RoomObject(0x1FD59A, [0xA3, 0x23, 0xFA])),
+ Pot(80, 28, PotItem.Nothing, 'Thieves Ambush', obj=RoomObject(0x1FD5A3, [0xA3, 0xE3, 0xFA])),
+ Pot(88, 16, PotItem.Heart, 'Thieves Ambush', obj=RoomObject(0x1FD59D, [0xB3, 0x83, 0xFA])),
+ Pot(88, 28, PotItem.FiveRupees, 'Thieves Ambush', obj=RoomObject(0x1FD5A0, [0xB3, 0xE3, 0xFA]))],
+ 0xCC: [Pot(36, 4, PotItem.FiveRupees, 'Thieves Rail Ledge', obj=RoomObject(0x1FD6F0, [0x4B, 0x23, 0xFA])),
+ Pot(36, 28, PotItem.FiveRupees, 'Thieves Rail Ledge', obj=RoomObject(0x1FD6F3, [0x4B, 0xE3, 0xFA])),
+ Pot(112, 4, PotItem.Heart, 'Thieves BK Corner', obj=RoomObject(0x1FD6ED, [0xE3, 0x23, 0xFA])),
+ Pot(112, 28, PotItem.Bomb, 'Thieves BK Corner', obj=RoomObject(0x1FD6F6, [0xE3, 0xE3, 0xFA]))],
+ 0xCE: [Pot(76, 8, PotItem.SmallMagic, 'Ice Antechamber', obj=RoomObject(0x1FCAA7, [0x9B, 0x43, 0xFA])),
+ Pot(80, 8, PotItem.SmallMagic, 'Ice Antechamber', obj=RoomObject(0x1FCAAA, [0xA3, 0x43, 0xFA])),
+ Pot(108, 12, PotItem.Bomb, 'Ice Antechamber', obj=RoomObject(0x1FCAB9, [0xDB, 0x63, 0xFA])),
+ Pot(112, 12, PotItem.FiveArrows, 'Ice Antechamber', obj=RoomObject(0x1FCABC, [0xE3, 0x63, 0xFA])),
+ Pot(204, 11, PotItem.Hole, 'Ice Antechamber'),
+ Pot(0x6c, 8, PotItem.Nothing, 'Ice Antechamber', PotFlags.Block)],
+ 0xD0: [Pot(158, 5, PotItem.SmallMagic, 'Tower Dark Maze', obj=RoomObject(0x1F90A5, [0x3F, 0x2F, 0xFA])),
+ Pot(140, 11, PotItem.OneRupee, 'Tower Dark Maze', obj=RoomObject(0x1F90A8, [0x1B, 0x5F, 0xFA])),
+ Pot(42, 13, PotItem.SmallMagic, 'Tower Dark Maze', obj=RoomObject(0x1F90AE, [0x57, 0x6B, 0xFA])),
+ Pot(48, 16, PotItem.Heart, 'Tower Dark Maze', obj=RoomObject(0x1F90B1, [0x63, 0x83, 0xFA])),
+ Pot(176, 20, PotItem.OneRupee, 'Tower Dark Maze', obj=RoomObject(0x1F90B4, [0x63, 0xA7, 0xFA])),
+ Pot(146, 23, PotItem.FiveRupees, 'Tower Dark Maze', obj=RoomObject(0x1F90B7, [0x27, 0xBF, 0xFA])),
+ Pot(12, 28, PotItem.Heart, 'Tower Dark Maze', obj=RoomObject(0x1F90BA, [0x1B, 0xE3, 0xFA]))],
+ 0xD1: [Pot(48, 4, PotItem.BigMagic, 'Mire Conveyor Barrier', obj=RoomObject(0x1FB22C, [0x63, 0x23, 0xFA])),
+ Pot(168, 7, PotItem.OneRupee, 'Mire Conveyor Barrier', obj=RoomObject(0x1FB229, [0x53, 0x3F, 0xFA])),
+ Pot(76, 4, PotItem.OneRupee, 'Mire Neglected Room', obj=RoomObject(0x1FB23B, [0x9B, 0x23, 0xFA])),
+ Pot(112, 4, PotItem.FiveArrows, 'Mire Neglected Room', obj=RoomObject(0x1FB250, [0xE3, 0x23, 0xFA])),
+ Pot(76, 12, PotItem.Nothing, 'Mire Neglected Room', obj=RoomObject(0x1FB232, [0x9B, 0x63, 0xFA])),
+ Pot(112, 12, PotItem.OneRupee, 'Mire Neglected Room', obj=RoomObject(0x1FB235, [0xE3, 0x63, 0xFA]))],
+ 0xD6: [Pot(92, 22, PotItem.BigMagic, 'TR Main Lobby', obj=RoomObject(0x1FE0F9, [0xBB, 0xB3, 0xFA])),
+ Pot(96, 22, PotItem.Bomb, 'TR Main Lobby', obj=RoomObject(0x1FE0FC, [0xC3, 0xB3, 0xFA]))],
+ 0xD8: [Pot(202, 8, PotItem.Heart, 'Eastern Duo Eyegores', obj=RoomObject(0x0A95C0, [0x97, 0x47, 0xFA])),
+ Pot(242, 8, PotItem.FiveArrows, 'Eastern Duo Eyegores', obj=RoomObject(0x0A95CF, [0xE7, 0x47, 0xFA])),
+ Pot(202, 10, PotItem.FiveArrows, 'Eastern Duo Eyegores', obj=RoomObject(0x0A95C3, [0x97, 0x57, 0xFA])),
+ Pot(242, 10, PotItem.FiveArrows, 'Eastern Duo Eyegores', obj=RoomObject(0x0A95C9, [0xE7, 0x57, 0xFA])),
+ Pot(202, 12, PotItem.FiveArrows, 'Eastern Duo Eyegores', obj=RoomObject(0x0A95C6, [0x97, 0x67, 0xFA])),
+ Pot(242, 12, PotItem.Heart, 'Eastern Duo Eyegores', obj=RoomObject(0x0A95CC, [0xE7, 0x67, 0xFA])),
+ Pot(92, 24, PotItem.Heart, 'Eastern Single Eyegore', obj=RoomObject(0x0A95DB, [0xBB, 0xC3, 0xFA])),
+ Pot(96, 24, PotItem.FiveArrows, 'Eastern Single Eyegore', obj=RoomObject(0x0A95DE, [0xC3, 0xC3, 0xFA]))],
+ 0xD9: [Pot(92, 20, PotItem.FiveArrows, 'Eastern False Switches', obj=RoomObject(0x0A965A, [0xBB, 0xA3, 0xFA])),
+ Pot(92, 28, PotItem.Heart, 'Eastern False Switches', obj=RoomObject(0x0A965D, [0xBB, 0xE3, 0xFA]))],
+ 0xDA: [Pot(24, 23, PotItem.FiveArrows, 'Eastern Attic Start', obj=RoomObject(0x0A968B, [0x33, 0xBB, 0xFA])),
+ Pot(36, 23, PotItem.FiveArrows, 'Eastern Attic Start', obj=RoomObject(0x0A968E, [0x4B, 0xBB, 0xFA])),
+ Pot(24, 25, PotItem.Switch, 'Eastern Attic Start', obj=RoomObject(0x0A9691, [0x33, 0xCB, 0xFA])),
+ Pot(36, 25, PotItem.Heart, 'Eastern Attic Start', obj=RoomObject(0x0A9694, [0x4B, 0xCB, 0xFA]))],
+ 0xDB: [Pot(12, 4, PotItem.Nothing, 'Thieves Lobby', obj=RoomObject(0x1FD2C5, [0x1B, 0x23, 0xFA])),
+ Pot(62, 19, PotItem.Nothing, 'Thieves Lobby', PotFlags.LowerRegion, RoomObject(0x1FD300, [0x7F, 0x9B, 0xFA])),
+ Pot(112, 4, PotItem.FiveRupees, 'Thieves Lobby', obj=RoomObject(0x1FD2C8, [0xE3, 0x23, 0xFA])),
+ Pot(88, 16, PotItem.Heart, 'Thieves Lobby', obj=RoomObject(0x1FD2CB, [0xB3, 0x83, 0xFA]))],
+ 0xDC: [Pot(56, 4, PotItem.FiveRupees, 'Thieves Compass Room', obj=RoomObject(0x1FD447, [0x73, 0x23, 0xFA])),
+ Pot(112, 4, PotItem.Bomb, 'Thieves Compass Room', obj=RoomObject(0x1FD44A, [0xE3, 0x23, 0xFA])),
+ Pot(68, 16, PotItem.Heart, 'Thieves Compass Room', obj=RoomObject(0x1FD44D, [0x8B, 0x83, 0xFA])),
+ Pot(12, 28, PotItem.FiveArrows, 'Thieves Compass Room', obj=RoomObject(0x1FD453, [0x1B, 0xE3, 0xFA]))],
+ 0xE3: [Pot(88, 0x17, PotItem.Nothing, 'Bat Cave (right)', PotFlags.LowerRegion, RoomObject(0x0A82EE, [0xB3, 0xBB, 0xFA])),
+ Pot(100, 0x19, PotItem.OneRupee, 'Bat Cave (right)', PotFlags.LowerRegion, RoomObject(0x0A82F1, [0xCB, 0xCB, 0xFA]))],
+ 0xE4: [Pot(32, 9, PotItem.FiveRupees, 'Old Man House', obj=RoomObject(0x0AB42A, [0x43, 0x4B, 0xFA])),
+ Pot(112, 10, PotItem.OneRupee, 'Old Man House', obj=RoomObject(0x0AB46C, [0xE3, 0x53, 0xFA]))],
+ 0xE5: [Pot(48, 4, PotItem.OneRupee, 'Old Man House Back', obj=RoomObject(0x0AB545, [0x63, 0x23, 0xFA])),
+ Pot(76, 4, PotItem.OneRupee, 'Old Man House Back', obj=RoomObject(0x0AB548, [0x9B, 0x23, 0xFA])),
+ Pot(112, 16, PotItem.OneRupee, 'Old Man House Back', obj=RoomObject(0x0AB54B, [0xE3, 0x83, 0xFA])),
+ Pot(64, 18, PotItem.FiveRupees, 'Old Man House Back', obj=RoomObject(0x0AB54E, [0x83, 0x93, 0xFA]))],
+ 0xE6: [Pot(108, 12, PotItem.FiveArrows, 'Death Mountain Return Cave (left)', obj=RoomObject(0x0AB29D, [0xDB, 0x63, 0xFA])),
+ Pot(88, 16, PotItem.Heart, 'Death Mountain Return Cave (left)', obj=RoomObject(0x0AB2A0, [0xB3, 0x83, 0xFA])),
+ Pot(72, 20, PotItem.Nothing, 'Death Mountain Return Cave (left)', obj=RoomObject(0x0AB2A3, [0x93, 0xA3, 0xFA])),
+ Pot(56, 24, PotItem.OneRupee, 'Death Mountain Return Cave (left)', obj=RoomObject(0x0AB2A6, [0x73, 0xC3, 0xFA]))],
+ 0xE7: [Pot(68, 5, PotItem.OneRupee, 'Death Mountain Return Cave (right)', obj=RoomObject(0x0AB389, [0x8B, 0x2B, 0xFA])),
+ Pot(72, 5, PotItem.OneRupee, 'Death Mountain Return Cave (right)', obj=RoomObject(0x0AB38C, [0x93, 0x2B, 0xFA]))],
+ 0xE8: [Pot(96, 4, PotItem.Heart, 'Superbunny Cave (Bottom)', obj=RoomObject(0x0AA98E, [0xC3, 0x23, 0xFA]))],
+ 0xEB: [Pot(206, 8, PotItem.FiveRupees, 'Bumper Cave', obj=RoomObject(0x0AADE7, [0x9F, 0x47, 0xFA])),
+ Pot(210, 8, PotItem.FiveRupees, 'Bumper Cave', obj=RoomObject(0x0AADEA, [0xA7, 0x47, 0xFA])),
+ Pot(88, 14, PotItem.SmallMagic, 'Bumper Cave', obj=RoomObject(0x0AADED, [0xB3, 0x73, 0xFA])),
+ Pot(92, 14, PotItem.Heart, 'Bumper Cave', obj=RoomObject(0x0AADF0, [0xBB, 0x73, 0xFA])),
+ Pot(96, 14, PotItem.SmallMagic, 'Bumper Cave', obj=RoomObject(0x0AADF3, [0xC3, 0x73, 0xFA]))],
+ 0xF1: [Pot(64, 5, PotItem.Heart, 'Old Man Cave', obj=RoomObject(0x0AA6B2, [0x83, 0x2B, 0xFA]))],
+ 0xF3: [Pot(0x28, 0x14, PotItem.Nothing, 'Elder House', obj=RoomObject(0x0AA76F, [0x53, 0xA3, 0xFA])),
+ Pot(0x2C, 0x14, PotItem.Nothing, 'Elder House', obj=RoomObject(0x0AA772, [0x5B, 0xA3, 0xFA])),
+ Pot(0x30, 0x14, PotItem.Nothing, 'Elder House', obj=RoomObject(0x0AA775, [0x63, 0xA3, 0xFA]))],
+ 0xF8: [Pot(242, 13, PotItem.BigMagic, 'Superbunny Cave (Top)', obj=RoomObject(0x0AA8B9, [0xE7, 0x6F, 0xFA]))],
+ 0xFD: [Pot(88, 6, PotItem.FiveRupees, 'Fairy Ascension Cave (Top)', obj=RoomObject(0x0AAA51, [0xB3, 0x33, 0xFA])),
+ Pot(100, 6, PotItem.FiveRupees, 'Fairy Ascension Cave (Top)', obj=RoomObject(0x0AAA54, [0xCB, 0x33, 0xFA])),
+ Pot(84, 23, PotItem.FiveRupees, 'Fairy Ascension Cave (Bottom)', obj=RoomObject(0x0AAA57, [0xAB, 0xBB, 0xFA])),
+ Pot(84, 24, PotItem.FiveRupees, 'Fairy Ascension Cave (Bottom)', obj=RoomObject(0x0AAA5A, [0xAB, 0xC3, 0xFA]))],
+ 0xFF: [Pot(92, 8, PotItem.Heart, 'Paradox Cave Bomb Area', obj=RoomObject(0x0AAC5B, [0xBB, 0x43, 0xFA])),
+ Pot(96, 8, PotItem.Heart, 'Paradox Cave Bomb Area', obj=RoomObject(0x0AAC5E, [0xC3, 0x43, 0xFA])),
+ Pot(112, 28, PotItem.OneRupee, 'Paradox Cave Front', obj=RoomObject(0x0AAC6A, [0xE3, 0xE3, 0xFA]))],
+ 0x101: [Pot(12, 20, PotItem.Heart, 'Snitch Lady (East)', obj=RoomObject(0x03EBE5, [0x1B, 0xA3, 0xFA])),
+ Pot(224, 19, PotItem.Chicken, 'Snitch Lady (West)', obj=RoomObject(0x03EC1E, [0xC3, 0x9F, 0xFA])),
+ Pot(228, 19, PotItem.Heart, 'Snitch Lady (West)', obj=RoomObject(0x03EC21, [0xCB, 0x9F, 0xFA]))],
+ 0x102: [Pot(146, 19, PotItem.Heart, 'Sick Kids House', obj=RoomObject(0x03EC81, [0x27, 0x9F, 0xFA])),
+ Pot(150, 19, PotItem.Heart, 'Sick Kids House', obj=RoomObject(0x03EC84, [0x2F, 0x9F, 0xFA]))],
+ 0x103: [Pot(140, 7, PotItem.Chicken, 'Tavern', obj=RoomObject(0x03ECDA, [0x1B, 0x3F, 0xFA])),
+ Pot(140, 9, PotItem.Nothing, 'Tavern', obj=RoomObject(0x03ECDD, [0x1B, 0x4F, 0xFA])),
+ Pot(12, 12, PotItem.Heart, 'Tavern (Front)', obj=RoomObject(0x03ECE6, [0x1B, 0x63, 0xFA]))],
+ 0x104: [Pot(202, 21, PotItem.Heart, 'Links House', obj=RoomObject(0x0A8017, [0x97, 0xAF, 0xFA])),
+ Pot(202, 22, PotItem.Heart, 'Links House', obj=RoomObject(0x0A801A, [0x97, 0xB7, 0xFA])),
+ Pot(202, 23, PotItem.Heart, 'Links House', obj=RoomObject(0x0A801D, [0x97, 0xBF, 0xFA]))],
+ 0x105: [Pot(30, 20, PotItem.Heart, 'Sahasrahlas Hut', obj=RoomObject(0x03EDF2, [0x3F, 0xA3, 0xFA])),
+ Pot(28, 21, PotItem.Heart, 'Sahasrahlas Hut', obj=RoomObject(0x03EDEC, [0x3B, 0xAB, 0xFA])),
+ Pot(32, 21, PotItem.Heart, 'Sahasrahlas Hut', obj=RoomObject(0x03EDEF, [0x43, 0xAB, 0xFA]))],
+ 0x106: [Pot(0x6c, 0x1b, PotItem.Nothing, 'Brewery', obj=RoomObject(0x03EEB0, [0xDB, 0xDB, 0xFA]))],
+ 0x107: [Pot(214, 23, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF49, [0xAF, 0xBF, 0xFA])),
+ Pot(222, 23, PotItem.FiveArrows, 'Light World Bomb Hut', obj=RoomObject(0x03EF4C, [0xBF, 0xBF, 0xFA])),
+ Pot(230, 23, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF4F, [0xCF, 0xBF, 0xFA])),
+ Pot(214, 25, PotItem.OneRupee, 'Light World Bomb Hut', obj=RoomObject(0x03EF52, [0xAF, 0xCF, 0xFA])),
+ Pot(222, 25, PotItem.Nothing, 'Light World Bomb Hut', obj=RoomObject(0x03EF55, [0xBF, 0xCF, 0xFA])),
+ Pot(230, 25, PotItem.OneRupee, 'Light World Bomb Hut', obj=RoomObject(0x03EF58, [0xCF, 0xCF, 0xFA])),
+ Pot(214, 27, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF5B, [0xAF, 0xDF, 0xFA])),
+ Pot(230, 27, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF5E, [0xCF, 0xDF, 0xFA]))],
+ 0x108: [Pot(166, 19, PotItem.Chicken, 'Chicken House', obj=RoomObject(0x03EFA9, [0x4F, 0x9F, 0xFA]))],
+ 0x10C: [Pot(88, 14, PotItem.Heart, 'Hookshot Fairy', obj=RoomObject(0x03F329, [0xB3, 0x73, 0xFA]))],
+ # note: these addresses got moved thanks to waterfall fairy edit
+ 0x114: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79A, [0xBB, 0x23, 0xFA])),
+ Pot(96, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79D, [0xC3, 0x23, 0xFA])),
+ Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A0, [0xBB, 0x2B, 0xFA])),
+ Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A3, [0xC3, 0x2B, 0xFA])),
+ Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint', obj=RoomObject(0x03F7A6, [0xBB, 0x53, 0xFA])),
+ Pot(96, 10, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A9, [0xC3, 0x53, 0xFA]))],
+ 0x117: [Pot(138, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB2, [0x17, 0x1F, 0xFA])), # 0x38A -> 38A
+ Pot(142, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB8, [0x1F, 0x1F, 0xFA])),
+ Pot(166, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCC1, [0x4F, 0x1F, 0xFA])),
+ Pot(170, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCC7, [0x57, 0x1F, 0xFA])),
+ Pot(138, 4, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB5, [0x17, 0x27, 0xFA])),
+ Pot(142, 4, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCBB, [0x1F, 0x27, 0xFA])),
+ Pot(166, 4, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCC4, [0x4F, 0x27, 0xFA])),
+ Pot(170, 4, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCCA, [0x57, 0x27, 0xFA])),
+ Pot(0x18, 0x8, PotItem.Nothing, 'Spike Cave', PotFlags.Block)],
+ 0x119: [Pot(44, 28, PotItem.Heart, 'Blinds Hideout', obj=RoomObject(0x03FDFB, [0x5B, 0xE3, 0xFA])),
+ Pot(48, 28, PotItem.OneRupee, 'Blinds Hideout', obj=RoomObject(0x03FDFE, [0x63, 0xE3, 0xFA])),
+ Pot(76, 28, PotItem.Heart, 'Blinds Hideout', obj=RoomObject(0x03FE01, [0x9B, 0xE3, 0xFA])),
+ Pot(80, 28, PotItem.Heart, 'Blinds Hideout', obj=RoomObject(0x03FE04, [0xA3, 0xE3, 0xFA]))],
+ 0x11A: [Pot(214, 10, PotItem.Heart, 'Palace of Darkness Hint', obj=RoomObject(0x03F91F, [0xAF, 0x57, 0xFA])),
+ Pot(218, 10, PotItem.Heart, 'Palace of Darkness Hint', obj=RoomObject(0x03F922, [0xB7, 0x57, 0xFA])),
+ Pot(226, 10, PotItem.Heart, 'Palace of Darkness Hint', obj=RoomObject(0x03F925, [0xC7, 0x57, 0xFA])),
+ Pot(230, 10, PotItem.Heart, 'Palace of Darkness Hint', obj=RoomObject(0x03F928, [0xCF, 0x57, 0xFA]))],
+ 0x11B: [Pot(24, 0x15, PotItem.Nothing, 'Cave 45', PotFlags.LowerRegion, RoomObject(0x03F416, [0x33, 0xAB, 0xFA])),
+ Pot(24, 0x16, PotItem.Heart, 'Cave 45', PotFlags.LowerRegion, obj=RoomObject(0x03F419, [0x33, 0xB3, 0xFA])),
+ Pot(32, 0x16, PotItem.Heart, 'Cave 45', PotFlags.LowerRegion, obj=RoomObject(0x03F422, [0x43, 0xB3, 0xFA])),
+ Pot(40, 0x16, PotItem.Heart, 'Cave 45', PotFlags.LowerRegion, obj=RoomObject(0x03F425, [0x53, 0xB3, 0xFA])),
+ Pot(24, 0x17, PotItem.Heart, 'Cave 45', PotFlags.LowerRegion, obj=RoomObject(0x03F41C, [0x33, 0xBB, 0xFA])),
+ Pot(28, 0x18, PotItem.Heart, 'Cave 45', PotFlags.LowerRegion, obj=RoomObject(0x03F41F, [0x3B, 0xC3, 0xFA])),
+ Pot(92, 22, PotItem.Bomb, 'Graveyard Cave', obj=RoomObject(0x03F3D8, [0xBB, 0xB3, 0xFA])),
+ Pot(96, 22, PotItem.Heart, 'Graveyard Cave', obj=RoomObject(0x03F3DB, [0xC3, 0xB3, 0xFA])),
+ Pot(92, 23, PotItem.Bomb, 'Graveyard Cave', obj=RoomObject(0x03F3DE, [0xBB, 0xBB, 0xFA])),
+ Pot(96, 23, PotItem.Heart, 'Graveyard Cave', obj=RoomObject(0x03F3E1, [0xC3, 0xBB, 0xFA])),
+ Pot(92, 24, PotItem.Bomb, 'Graveyard Cave', obj=RoomObject(0x03F3E4, [0xBB, 0xC3, 0xFA])),
+ Pot(96, 24, PotItem.Heart, 'Graveyard Cave', obj=RoomObject(0x03F3E7, [0xC3, 0xC3, 0xFA])),
+ Pot(92, 25, PotItem.Bomb, 'Graveyard Cave', obj=RoomObject(0x03F3EA, [0xBB, 0xCB, 0xFA])),
+ Pot(96, 25, PotItem.Heart, 'Graveyard Cave', obj=RoomObject(0x03F3ED, [0xC3, 0xCB, 0xFA]))],
+ 0x11D: [Pot(60, 6, PotItem.FiveRupees, 'Blinds Hideout (Top)', obj=RoomObject(0x03FE69, [0x7B, 0x33, 0xFA])),
+ Pot(64, 6, PotItem.FiveRupees, 'Blinds Hideout (Top)', obj=RoomObject(0x03FE6C, [0x83, 0x33, 0xFA])),
+ Pot(60, 7, PotItem.FiveRupees, 'Blinds Hideout (Top)', obj=RoomObject(0x03FE6F, [0x7B, 0x3B, 0xFA])),
+ Pot(64, 7, PotItem.FiveRupees, 'Blinds Hideout (Top)', obj=RoomObject(0x03FE72, [0x83, 0x3B, 0xFA])),
+ Pot(60, 8, PotItem.FiveRupees, 'Blinds Hideout (Top)', obj=RoomObject(0x03FE75, [0x7B, 0x43, 0xFA])),
+ Pot(64, 8, PotItem.FiveRupees, 'Blinds Hideout (Top)', obj=RoomObject(0x03FE78, [0x83, 0x43, 0xFA]))],
+ 0x11F: [Pot(174, 28, PotItem.Heart, 'Lumberjack House', obj=RoomObject(0x03FF8A, [0x5F, 0xE7, 0xFA])),
+ Pot(178, 28, PotItem.Heart, 'Lumberjack House', obj=RoomObject(0x03FF8D, [0x67, 0xE7, 0xFA]))],
+ 0x124: [Pot(20, 20, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5C2, [0x2B, 0xA3, 0xFA])),
+ Pot(40, 20, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5E3, [0x53, 0xA3, 0xFA])),
+ Pot(20, 21, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5C5, [0x2B, 0xAB, 0xFA])),
+ Pot(40, 21, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5E0, [0x53, 0xAB, 0xFA])),
+ Pot(20, 22, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5C8, [0x2B, 0xB3, 0xFA])),
+ Pot(40, 22, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5DD, [0x53, 0xB3, 0xFA])),
+ Pot(24, 24, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5CE, [0x33, 0xC3, 0xFA])),
+ Pot(28, 24, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5D1, [0x3B, 0xC3, 0xFA])),
+ Pot(32, 24, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5D4, [0x43, 0xC3, 0xFA])),
+ Pot(36, 24, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5D7, [0x4B, 0xC3, 0xFA]))],
+ 0x125: [Pot(24, 25, PotItem.FiveRupees, '20 Rupee Cave', obj=RoomObject(0x0AB618, [0x33, 0xCB, 0xFA])),
+ Pot(28, 25, PotItem.FiveRupees, '20 Rupee Cave', obj=RoomObject(0x0AB61B, [0x3B, 0xCB, 0xFA])),
+ Pot(32, 25, PotItem.FiveRupees, '20 Rupee Cave', obj=RoomObject(0x0AB61E, [0x43, 0xCB, 0xFA])),
+ Pot(36, 25, PotItem.FiveRupees, '20 Rupee Cave', obj=RoomObject(0x0AB621, [0x4B, 0xCB, 0xFA])),
+ Pot(88, 22, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB627, [0xB3, 0xB3, 0xFA])),
+ Pot(100, 22, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB62A, [0xCB, 0xB3, 0xFA])),
+ Pot(88, 28, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB633, [0xB3, 0xE3, 0xFA])),
+ Pot(100, 28, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB636, [0xCB, 0xE3, 0xFA]))],
+ 0x127: [Pot(24, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A801A, [0x33, 0xCB, 0xFA])),
+ Pot(28, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A801D, [0x3B, 0xCB, 0xFA])),
+ Pot(32, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A8020, [0x43, 0xCB, 0xFA])),
+ Pot(36, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A8023, [0x4B, 0xCB, 0xFA]))],
}
def shuffle_pots(world, player):
import RaceRandom as random
- new_pot_contents = {}
+ new_pot_contents = PotSecretTable()
- for supertile in vanilla_pots:
- old_pots = vanilla_pots[supertile]
+ for super_tile in vanilla_pots:
+ old_pots = vanilla_pots[super_tile]
new_pots = [Pot(pot.x, pot.y, PotItem.Nothing, pot.room, pot.flags) for pot in old_pots]
# sort in the order Hole, Switch, Key, Other, Nothing
sort_order = {PotItem.Hole: 4, PotItem.Switch: 3, PotItem.Key: 2, PotItem.Nothing: 0}
@@ -295,7 +879,7 @@ def shuffle_pots(world, player):
elif old_pot.item == PotItem.Switch:
available_pots = (pot for pot in new_pots if (pot.room == old_pot.room or pot.room in movable_switch_rooms[old_pot.room]) and not (pot.flags & PotFlags.NoSwitch))
elif old_pot.item == PotItem.Key:
- if world.doorShuffle[player] == 'vanilla' and not world.retro[player] and not world.keydropshuffle[player] and world.logic[player] != 'nologic':
+ if world.doorShuffle[player] == 'vanilla' and not world.retro[player] and world.pottery[player] == 'none' and world.logic[player] != 'nologic':
available_pots = (pot for pot in new_pots if pot.room not in invalid_key_rooms)
else:
available_pots = new_pots
@@ -309,12 +893,14 @@ def shuffle_pots(world, player):
if world.retro[player] and new_pot.item == PotItem.FiveArrows:
new_pot.item = PotItem.FiveRupees
- if new_pot.item == PotItem.Key and new_pot.room != old_pot.room:
- # Move pot key to new room
+ if new_pot.item == PotItem.Key:
key = next(location for location in world.get_region(old_pot.room, player).locations if location.name in key_drop_data)
- world.get_region(old_pot.room, player).locations.remove(key)
- world.get_region(new_pot.room, player).locations.append(key)
- key.parent_region = world.get_region(new_pot.room, player)
+ key.pot = new_pot
+ if new_pot.room != old_pot.room:
+ # Move pot key to new room
+ world.get_region(old_pot.room, player).locations.remove(key)
+ world.get_region(new_pot.room, player).locations.append(key)
+ key.parent_region = world.get_region(new_pot.room, player)
elif new_pot.item == PotItem.Switch and (new_pot.flags & PotFlags.SwitchLogicChange):
if new_pot.room == 'PoD Basement Ledge':
basement = world.get_region(old_pot.room, player)
@@ -328,8 +914,163 @@ def shuffle_pots(world, player):
# Rule is created based on barrier
world.get_door('Thieves Attic ES', player).barrier(CrystalBarrier.Orange)
else:
- raise Exception("Switch locattion in room %s requires logic change" % new_pot.room)
+ raise Exception("Switch location in room %s requires logic change" % new_pot.room)
- new_pot_contents[supertile] = new_pots
+ new_pot_contents.room_map[super_tile] = new_pots
world.pot_contents[player] = new_pot_contents
+
+
+def shuffle_pot_switches(world, player):
+ import RaceRandom as random
+
+ for super_tile in vanilla_pots:
+ new_pots = world.pot_contents[player].room_map[super_tile]
+ # sort in the order Hole, Switch, Key, Other, Nothing
+ sort_order = {PotItem.Hole: 4, PotItem.Switch: 3, PotItem.Key: 2, PotItem.Nothing: 0}
+ old_pots = sorted(new_pots, key=lambda pot: sort_order.get(pot.item, 1), reverse=True)
+
+ for old_pot in old_pots:
+ if old_pot.item != PotItem.Switch:
+ break
+ else:
+ available_pots = [pot for pot in new_pots if (pot.room == old_pot.room or pot.room in movable_switch_rooms[old_pot.room]) and not (pot.flags & PotFlags.NoSwitch)]
+
+ new_pot = random.choice(available_pots)
+ new_pot.item, old_pot.item = old_pot.item, new_pot.item
+ if world.retro[player] and new_pot.item == PotItem.FiveArrows:
+ new_pot.item = PotItem.FiveRupees
+
+ if new_pot.item == PotItem.Switch and (new_pot.flags & PotFlags.SwitchLogicChange):
+ if new_pot.room == 'PoD Basement Ledge':
+ basement = world.get_region(old_pot.room, player)
+ ledge = world.get_region(new_pot.room, player)
+ ledge.locations.append(basement.locations.pop())
+ elif new_pot.room == 'Swamp Push Statue':
+ from Rules import set_rule
+ set_rule(world.get_entrance('Swamp Push Statue NE', player), lambda state: state.has('Cane of Somaria', player))
+ world.get_door('Swamp Push Statue NW', player).blocked = True
+ elif new_pot.room == 'Thieves Attic Hint':
+ # Rule is created based on barrier
+ world.get_door('Thieves Attic ES', player).barrier(CrystalBarrier.Orange)
+ else:
+ raise Exception("Switch location in room %s requires logic change" % new_pot.room)
+ for location in world.get_locations():
+ if location.player == player and location.type == LocationType.Pot and location.pot.item == PotItem.Switch:
+ location.real = False
+ if location in world.dynamic_locations:
+ world.dynamic_locations.remove(location)
+ location.parent_region.locations.remove(location)
+ world.clear_location_cache()
+
+
+def choose_pots(world, player):
+ pot_pool = set()
+ if world.pottery[player] == 'reduced':
+ dungeon_list = []
+ for super_tile, pot_list in vanilla_pots.items():
+ for pot in pot_list:
+ if world.get_region(pot.room, player).type == RegionType.Cave:
+ pot_pool.add(pot)
+ else:
+ dungeon_list.append(pot)
+ k = len(dungeon_list) // 4
+ pot_pool.update(random.choices(dungeon_list, k=k))
+ elif world.pottery[player] == 'clustered':
+ dungeon_map = defaultdict(list)
+ dungeon_count = 0
+ for super_tile, pot_list in vanilla_pots.items():
+ for pot in pot_list:
+ if world.get_region(pot.room, player).type == RegionType.Cave:
+ pot_pool.add(pot)
+ else:
+ dungeon_map[pot.room].append(pot)
+ dungeon_count += 1
+ k = dungeon_count // 2
+ options = sorted(list(dungeon_map.keys()))
+ chosen_amt = 0
+ while chosen_amt < k:
+ chosen_section = random.choice(options)
+ pot_pool.update(dungeon_map[chosen_section])
+ chosen_amt += len(dungeon_map[chosen_section])
+ options.remove(chosen_section)
+ return pot_pool
+
+
+key_drop_data = {
+ 'Hyrule Castle - Map Guard Key Drop': ['Drop', (0x09E20C, 0x72, 0), 'dropped in Hyrule Castle', 'Small Key (Escape)'],
+ 'Hyrule Castle - Boomerang Guard Key Drop': ['Drop', (0x09E204, 0x71, 1), 'dropped in Hyrule Castle', 'Small Key (Escape)'],
+ 'Hyrule Castle - Key Rat Key Drop': ['Drop', (0x09DB80, 0x21, 0), 'dropped in Hyrule Castle', 'Small Key (Escape)'],
+ 'Hyrule Castle - Big Key Drop': ['Drop', (0x09E327, 0x80, 2), 'dropped in Hyrule Castle', 'Big Key (Escape)'],
+ 'Eastern Palace - Dark Square Pot Key': ['Pot', 0xBA, 'in a pot in Eastern Palace', 'Small Key (Eastern Palace)'],
+ 'Eastern Palace - Dark Eyegore Key Drop': ['Drop', (0x09E4F8, 0x99, 3), 'dropped in Eastern Palace', 'Small Key (Eastern Palace)'],
+ 'Desert Palace - Desert Tiles 1 Pot Key': ['Pot', 0x63, 'in a pot in Desert Palace', 'Small Key (Desert Palace)'],
+ 'Desert Palace - Beamos Hall Pot Key': ['Pot', 0x53, 'in a pot in Desert Palace', 'Small Key (Desert Palace)'],
+ 'Desert Palace - Desert Tiles 2 Pot Key': ['Pot', 0x43, 'in a pot in Desert Palace', 'Small Key (Desert Palace)'],
+ 'Castle Tower - Dark Archer Key Drop': ['Drop', (0x09E7C9, 0xC0, 3), 'dropped in Castle Tower', 'Small Key (Agahnims Tower)'],
+ 'Castle Tower - Circle of Pots Key Drop': ['Drop', (0x09E688, 0xB0, 10), 'dropped in Castle Tower', 'Small Key (Agahnims Tower)'],
+ 'Swamp Palace - Pot Row Pot Key': ['Pot', 0x38, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'],
+ 'Swamp Palace - Trench 1 Pot Key': ['Pot', 0x37, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'],
+ 'Swamp Palace - Hookshot Pot Key': ['Pot', 0x36, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'],
+ 'Swamp Palace - Trench 2 Pot Key': ['Pot', 0x35, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'],
+ 'Swamp Palace - Waterway Pot Key': ['Pot', 0x16, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'],
+ 'Skull Woods - West Lobby Pot Key': ['Pot', 0x56, 'in a pot in Skull Woods', 'Small Key (Skull Woods)'],
+ 'Skull Woods - Spike Corner Key Drop': ['Drop', (0x09DD74, 0x39, 1), 'dropped near Mothula', 'Small Key (Skull Woods)'],
+ "Thieves' Town - Hallway Pot Key": ['Pot', 0xBC, "in a pot in Thieves Town", 'Small Key (Thieves Town)'],
+ "Thieves' Town - Spike Switch Pot Key": ['Pot', 0xAB, "in a pot in Thieves Town", 'Small Key (Thieves Town)'],
+ 'Ice Palace - Jelly Key Drop': ['Drop', (0x09DA21, 0xE, 3), 'dropped in Ice Palace', 'Small Key (Ice Palace)'],
+ 'Ice Palace - Conveyor Key Drop': ['Drop', (0x09DE08, 0x3E, 8), 'dropped in Ice Palace', 'Small Key (Ice Palace)'],
+ 'Ice Palace - Hammer Block Key Drop': ['Pot', 0x3F, 'under a block in Ice Palace', 'Small Key (Ice Palace)'],
+ 'Ice Palace - Many Pots Pot Key': ['Pot', 0x9F, 'int a pot in Ice Palace', 'Small Key (Ice Palace)'],
+ 'Misery Mire - Spikes Pot Key': ['Pot', 0xB3, 'in a pot in Misery Mire', 'Small Key (Misery Mire)'],
+ 'Misery Mire - Fishbone Pot Key': ['Pot', 0xA1, 'in a pot in forgotten Mire', 'Small Key (Misery Mire)'],
+ 'Misery Mire - Conveyor Crystal Key Drop': ['Drop', (0x09E7FB, 0xC1, 9), 'dropped in Misery Mire', 'Small Key (Misery Mire)'],
+ 'Turtle Rock - Pokey 1 Key Drop': ['Drop', (0x09E70D, 0xB6, 5), 'dropped in Turtle Rock', 'Small Key (Turtle Rock)'],
+ 'Turtle Rock - Pokey 2 Key Drop': ['Drop', (0x09DA5D, 0x13, 6), 'dropped in Turtle Rock', 'Small Key (Turtle Rock)'],
+ 'Ganons Tower - Conveyor Cross Pot Key': ['Pot', 0x8B, "in a pot in Ganon's Tower", 'Small Key (Ganons Tower)'],
+ 'Ganons Tower - Double Switch Pot Key': ['Pot', 0x9B, "in a pot in Ganon's Tower", 'Small Key (Ganons Tower)'],
+ 'Ganons Tower - Conveyor Star Pits Pot Key': ['Pot', 0x7B, "in a pot in Ganon's Tower", 'Small Key (Ganons Tower)'],
+ 'Ganons Tower - Mini Helmasaur Key Drop': ['Drop', (0x09DDC4, 0x3D, 2), "dropped atop Ganon's Tower", 'Small Key (Ganons Tower)']
+}
+
+
+class PotSecretTable(object):
+ def __init__(self):
+ self.room_map = defaultdict(list)
+ self.multiworld_count = 0
+
+ def write_pot_data_to_rom(self, rom, colorize):
+ pointer_address = 0x140000 # pots currently in bank 28
+ pointer_offset = 0x128 * 2
+ empty_address = (pointer_address + pointer_offset)
+ empty_pointer = pc_to_snes(empty_address) & 0xFFFF
+ data_pointer = pointer_address + pointer_offset + 2
+ for room in range(0, 0x128):
+ if room in self.room_map:
+ list_idx = 0
+ collection_rate_mask = 0x00
+ data_address = pc_to_snes(data_pointer) & 0xFFFF
+ rom.write_bytes(pointer_address + room * 2, int16_as_bytes(data_address))
+ for pot in self.room_map[room]:
+ rom.write_bytes(data_pointer + list_idx * 3, pot.pot_data())
+ if pot.location is not None and not pot.location.forced_item:
+ collection_rate_mask |= 1 << (15 - list_idx)
+ if colorize and pot.obj_ref:
+ pot.obj_ref.change_type(Shuffled_Pot)
+ pot.obj_ref.write_to_rom(rom)
+ list_idx += 1
+ rom.write_bytes(data_pointer + list_idx * 3, [0xFF, 0xFF])
+ rom.write_bytes(snes_to_pc(0x28AA60) + room * 2, int16_as_bytes(collection_rate_mask))
+ data_pointer += 3 * list_idx + 2
+ else:
+ rom.write_bytes(pointer_address + room * 2, int16_as_bytes(empty_pointer))
+ rom.write_bytes(empty_address, [0xFF, 0xFF])
+
+ def size(self):
+ size = 0x128 * 2
+ for room in range(0, 0x128):
+ if room in self.room_map:
+ pot_list = [p for p in self.room_map[room]]
+ if pot_list:
+ size += len(pot_list) * 3 + 2
+ return size
diff --git a/README.md b/README.md
index e56bdfe3..f9d345c4 100644
--- a/README.md
+++ b/README.md
@@ -48,7 +48,7 @@ Alternatively, run ```Gui.py``` for a simple graphical user interface.
# Settings
-Only extra settings are found here. All door and entrance randomizer settings are supported. See their [readme](https://github.com/Aerinon/ALttPDoorRandomizer/blob/master/README.md)
+Only extra settings added by this Overworld Shuffle fork are found here. All door and entrance randomizer settings are supported. See their [readme](https://github.com/Aerinon/ALttPDoorRandomizer/blob/master/README.md)
## Overworld Layout Shuffle (--ow_shuffle)
@@ -68,8 +68,6 @@ OW Transitions are shuffled within each world separately.
This allows OW connections to be shuffled cross-world.
-'None (Allowed)' allows entrance connectors and whirlpools to result in cross-world behavior, but edge transitions will not. This isn't a recommended option.
-
Polar and Grouped both are guaranteed to result in two separated planes of tiles. To navigate to the other plane, you have the following methods: 1) Normal portals 2) Mirroring on DW tiles 3) Fluting to a LW tile that was previously unreachable
Limited and Chaos are not bound to follow a two-plane framework. This means that it could be possible to travel on foot to every tile without entering a normal portal.
@@ -136,11 +134,42 @@ New flute spots are chosen at random, with restrictions that limit the promixity
New flute spots are chosen at random with minimum bias.
+## Bonk Drop Shuffle (--bonk_drops)
+
+This adds 41 new item locations to the game. These bonk locations are limited to the ones that drop a static item in the vanilla game.
+
+- Bonk Locations consist of some trees, rocks, and statues
+ - 33 Trees
+ - 8 of the tree locations require Agahnim to be defeated to access the item
+ - 6 Rocks
+ - 1 of the rocks drops 2 items
+ - 1 Statue
+- Bonk locations can be collected by bonking into them with the Pegasus Boots or using the Quake Medallion
+- One of the bonk locations are guaranteed to have a full magic decanter
+- Some of the drops can be farmed repeatedly, but only increments the collection rate once
+- All of the bonk trees have been given an alternate color (and all non-bonk trees are reverted to normal tree color)
+ - Some screens are coded to change the "alternate tree color", some of them are strange (just how the vanilla game does it)
+ - Rocks and statues are unable to be made to have a different color
+- Since Fairies and Apples are new items that can appear in plain sight, they don't have a proper graphic for them yet. For now, they show up as Power Stars
+
+Future Note: This does NOT include the Good Bee (Cold Bee) Cave Statue...yet. In the future, this could be an additional item location.
+
+#### Items Added To Pool:
+- 15 Fairies
+- 8 Apples
+- 6 Bee Traps
+- 3 Red Rupees
+- 3 Blue Rupees
+- 2 Single Bomb
+- 2 Small Hearts
+- 1 Large Magic Decanter
+- 1 8x Bomb Pack
+
## New Goal Options (--goal)
### Trinity
-This goal gives you the choice between 3 goals, only one of which the player needs to complete: Defeat Ganon (no Aga2), Pulling Pedestal, or turn in TF pieces to Murahdulah. By default, you need to find 8 of 10 total TF pieces but this can be changed with a Custom Item Pool. It is recommended to set GT Entry to 7 crystals and Ganon to 5 crystals or Random crystals, although the player can flexibly change these settings as they seem fit.
+This goal gives you the choice between 3 goals, only one of which the player needs to complete: Defeat Ganon (no Aga2), Pulling Pedestal, or turn in TF pieces to Murahdahla. By default, you need to find 8 of 10 total TF pieces but this can be changed with a Custom Item Pool. It is recommended to set GT Entry to 7 crystals and Ganon to 5 crystals or Random crystals, although the player can flexibly change these settings as they seem fit.
## New Entrance Shuffle Options (--shuffle)
@@ -237,3 +266,9 @@ For randomizing the flute spots around the overworld
```
For specifying what item ganon will be vulnerable to while stunned in his final phase. (default: default)
+
+```
+--bonk_drops
+```
+
+This extends the item pool to bonk locations and makes them additional item locations
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 50cf5282..dcb80a60 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -1,91 +1,270 @@
-# New Features
+## New Features
-## Shuffle SFX
+## Pottery Lottery and Key Drop Shuffle Changes
-Shuffles a large portion of the sounds effects. Can be used with the adjuster.
+### Pottery
-CLI: ```--shuffle_sfx```
+New pottery option that control which pots (and large blocks) are in the locations pool:
+
+* None: No pots are in the pool, like normal randomizer
+* Key Pots: The pots that have keys are in the pool. This is about half of the old keydropshuffle option
+* Cave Pots: The pots that are not found in dungeons are in the pool. (Includes the large block in Spike Cave). Does
+not include key pots.
+* CaveKeys: Both non-dungeon pots and pots that used to have keys are in the pool.
+* Reduced: Same as CaveKeys but also roughly a quarter of dungeon pots are added to the location pool picked at random. This is a dynamic mode so pots in the pool will be colored. Pots out of the pool will have vanilla contents.
+* Clustered: LIke reduced but pot are grouped by logical sets and roughly 50% of pots are chosen from those group. This is a dynamic mode like the above.
+* Nonempty: All pots that had some sort of objects under them are chosen to be in the location pool. This excludes most large blocks and some pots out of dungeons.
+* Dungeon Pots: The pots that are in dungeons are in the pool. (Includes serveral large blocks)
+* Lottery: All pots and large blocks are in the pool
+
+By default, switches remain in their vanilla location (unless you turn on the legacy option below)
+
+CLI `--pottery