Merge branch 'main' into breaking_changes

# Conflicts:
#	Adjuster.py
#	AdjusterMain.py
#	BaseClasses.py
#	MultiClient.py
#	MultiServer.py
#	Mystery.py
#	Utils.py
#	WebHostLib/downloads.py
#	WebHostLib/generate.py
#	dumpSprites.py
#	test/TestBase.py
#	worlds/alttp/EntranceRandomizer.py
#	worlds/alttp/Main.py
#	worlds/alttp/Rom.py
This commit is contained in:
Fabian Dill
2021-01-03 13:13:59 +01:00
558 changed files with 13839 additions and 3095 deletions

View File

@@ -6,14 +6,10 @@ from Fill import FillError
def BossFactory(boss: str, player: int) -> Optional[Boss]:
if boss is None:
return None
if boss in boss_table:
enemizer_name, defeat_rule = boss_table[boss]
return Boss(boss, enemizer_name, defeat_rule, player)
logging.error('Unknown Boss: %s', boss)
return None
raise Exception('Unknown Boss: %s', boss)
def ArmosKnightsDefeatRule(state, player: int):
@@ -122,16 +118,15 @@ def GanonDefeatRule(state, player: int):
state.has_fire_source(player) and \
state.has('Silver Bow', player) and \
state.can_shoot_arrows(player)
easy_hammer = state.world.difficulty_adjustments[player] == "easy" and state.has("Hammer", player) and \
state.has('Silver Bow', player) and state.can_shoot_arrows(player)
can_hurt = state.has_beam_sword(player) or easy_hammer
can_hurt = state.has_beam_sword(player)
common = can_hurt and state.has_fire_source(player)
# silverless ganon may be needed in minor glitches
if state.world.logic[player] in {"owglitches", "minorglitches", "none"}:
# need to light torch a sufficient amount of times
return common and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (
state.has('Silver Bow', player) and state.can_shoot_arrows(player)) or
state.has('Lamp', player) or state.can_extend_magic(player, 12))
state.has('Lamp', player) or state.can_extend_magic(player, 12))
else:
return common and state.has('Silver Bow', player) and state.can_shoot_arrows(player)
@@ -153,27 +148,33 @@ boss_table = {
}
def can_place_boss(world, player: int, boss: str, dungeon_name: str, level: Optional[str] = None) -> bool:
if dungeon_name in ['Ganons Tower', 'Inverted Ganons Tower'] and level == 'top':
if boss in ["Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"]:
def can_place_boss(boss: str, dungeon_name: str, level: Optional[str] = None) -> bool:
# blacklist approach
if boss in {"Agahnim", "Agahnim2", "Ganon"}:
return False
if dungeon_name == 'Ganons Tower':
if level == 'top':
if boss in {"Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"}:
return False
elif level == 'middle':
if boss == "Blind":
return False
elif dungeon_name == 'Tower of Hera':
if boss in {"Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"}:
return False
if dungeon_name in ['Ganons Tower', 'Inverted Ganons Tower'] and level == 'middle':
if boss in ["Blind"]:
elif dungeon_name == 'Skull Woods':
if boss == "Trinexx":
return False
if dungeon_name == 'Tower of Hera' and boss in ["Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"]:
return False
if dungeon_name == 'Skull Woods' and boss in ["Trinexx"]:
return False
if boss in ["Agahnim", "Agahnim2", "Ganon"]:
return False
return True
def place_boss(world, player: int, boss: str, location: str, level: Optional[str]):
if location == 'Ganons Tower' and world.mode[player] == 'inverted':
location = 'Inverted Ganons Tower'
logging.debug('Placing boss %s at %s', boss, location + (' (' + level + ')' if level else ''))
world.get_dungeon(location, player).bosses[level] = BossFactory(boss, player)
@@ -182,85 +183,112 @@ def place_bosses(world, player: int):
if world.boss_shuffle[player] == 'none':
return
# Most to least restrictive order
if world.mode[player] != 'inverted':
boss_locations = [
['Ganons Tower', 'top'],
['Tower of Hera', None],
['Skull Woods', None],
['Ganons Tower', 'middle'],
['Eastern Palace', None],
['Desert Palace', None],
['Palace of Darkness', None],
['Swamp Palace', None],
['Thieves Town', None],
['Ice Palace', None],
['Misery Mire', None],
['Turtle Rock', None],
['Ganons Tower', 'bottom'],
]
else:
boss_locations = [
['Inverted Ganons Tower', 'top'],
['Tower of Hera', None],
['Skull Woods', None],
['Inverted Ganons Tower', 'middle'],
['Eastern Palace', None],
['Desert Palace', None],
['Palace of Darkness', None],
['Swamp Palace', None],
['Thieves Town', None],
['Ice Palace', None],
['Misery Mire', None],
['Turtle Rock', None],
['Inverted Ganons Tower', 'bottom'],
]
boss_locations = [
['Ganons Tower', 'top'],
['Tower of Hera', None],
['Skull Woods', None],
['Ganons Tower', 'middle'],
['Eastern Palace', None],
['Desert Palace', None],
['Palace of Darkness', None],
['Swamp Palace', None],
['Thieves Town', None],
['Ice Palace', None],
['Misery Mire', None],
['Turtle Rock', None],
['Ganons Tower', 'bottom'],
]
all_bosses = sorted(boss_table.keys()) # sorted to be deterministic on older pythons
placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']]
anywhere_bosses = [boss for boss in placeable_bosses if all(
can_place_boss(world, player, boss, loc, level) for loc, level in boss_locations)]
if world.boss_shuffle[player] in ["basic", "normal"]:
shuffle_mode = world.boss_shuffle[player]
already_placed_bosses = []
if ";" in shuffle_mode:
bosses = shuffle_mode.split(";")
shuffle_mode = bosses.pop()
for boss in bosses:
if "-" in boss:
loc, boss = boss.split("-")
boss = boss.title()
level = None
if loc.split(" ")[-1] in {"top", "middle", "bottom"}:
# split off level
loc = loc.split(" ")
level = loc[-1]
loc = " ".join(loc[:-1])
loc = loc.title()
if can_place_boss(boss, loc, level) and [loc, level] in boss_locations:
place_boss(world, player, boss, loc, level)
already_placed_bosses.append(boss)
boss_locations.remove([loc, level])
else:
Exception("Cannot place", boss, "at", loc, level, "for player", player)
else:
boss = boss.title()
boss_locations, already_placed_bosses = place_where_possible(world, player, boss, boss_locations)
if shuffle_mode == "none":
return # vanilla bosses come pre-placed
if shuffle_mode in ["basic", "normal"]:
if world.boss_shuffle[player] == "basic": # vanilla bosses shuffled
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
else: # all bosses present, the three duplicates chosen at random
bosses = all_bosses + [world.random.choice(placeable_bosses) for _ in range(3)]
# there is probably a better way to do this
while already_placed_bosses:
# remove already manually placed bosses, to prevent for example triple Lanmolas
boss = already_placed_bosses.pop()
if boss in bosses:
bosses.remove(boss)
# there may be more bosses than locations at this point, depending on manual placement
logging.debug('Bosses chosen %s', bosses)
world.random.shuffle(bosses)
for [loc, level] in boss_locations:
boss = next((b for b in bosses if can_place_boss(world, player, b, loc, level)), None)
for loc, level in boss_locations:
boss = next((b for b in bosses if can_place_boss(b, loc, level)), None)
if not boss:
loc_text = loc + (' (' + level + ')' if level else '')
raise FillError('Could not place boss for location %s' % loc_text)
bosses.remove(boss)
place_boss(world, player, boss, loc, level)
elif world.boss_shuffle[player] == "chaos": # all bosses chosen at random
for [loc, level] in boss_locations:
elif shuffle_mode == "chaos": # all bosses chosen at random
for loc, level in boss_locations:
try:
boss = world.random.choice(
[b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)])
[b for b in placeable_bosses if can_place_boss(b, loc, level)])
except IndexError:
loc_text = loc + (' (' + level + ')' if level else '')
raise FillError('Could not place boss for location %s' % loc_text)
else:
place_boss(world, player, boss, loc, level)
elif world.boss_shuffle[player] == "singularity":
elif shuffle_mode == "singularity":
primary_boss = world.random.choice(placeable_bosses)
remaining_boss_locations = []
for loc, level in boss_locations:
# place that boss where it can go
if can_place_boss(world, player, primary_boss, loc, level):
place_boss(world, player, primary_boss, loc, level)
else:
remaining_boss_locations.append((loc, level))
remaining_boss_locations, _ = place_where_possible(world, player, primary_boss, boss_locations)
if remaining_boss_locations:
# pick a boss to go into the remaining locations
remaining_boss = world.random.choice([boss for boss in placeable_bosses if all(
can_place_boss(world, player, boss, loc, level) for loc, level in remaining_boss_locations)])
for loc, level in remaining_boss_locations:
place_boss(world, player, remaining_boss, loc, level)
can_place_boss(boss, loc, level) for loc, level in remaining_boss_locations)])
remaining_boss_locations, _ = place_where_possible(world, player, remaining_boss, remaining_boss_locations)
if remaining_boss_locations:
raise Exception("Unfilled boss locations!")
else:
raise FillError(f"Could not find boss shuffle mode {world.boss_shuffle[player]}")
raise FillError(f"Could not find boss shuffle mode {shuffle_mode}")
def place_where_possible(world, player: int, boss: str, boss_locations):
remainder = []
placed_bosses = []
for loc, level in boss_locations:
# place that boss where it can go
if can_place_boss(boss, loc, level):
place_boss(world, player, boss, loc, level)
placed_bosses.append(boss)
else:
remainder.append((loc, level))
return remainder, placed_bosses

View File

@@ -9,7 +9,7 @@ def create_dungeons(world, player):
def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items):
dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.keyshuffle[player] == "universal" else small_keys,
dungeon_items, player)
dungeon.boss = BossFactory(default_boss, player)
dungeon.boss = BossFactory(default_boss, player) if default_boss else None
for region in dungeon.regions:
world.get_region(region, player).dungeon = dungeon
dungeon.world = world

View File

@@ -8,7 +8,7 @@ import shlex
import sys
from worlds.alttp.Main import main, get_seed
from worlds.alttp.Rom import get_sprite_from_name
from worlds.alttp.Rom import Sprite
from Utils import is_bundled, close_console
@@ -129,6 +129,14 @@ def parse_arguments(argv, no_defaults=False):
Timed mode. If time runs out, you lose (but can
still keep playing).
''')
parser.add_argument('--countdown_start_time', default=defval(10), type=int,
help='''Set amount of time, in minutes, to start with in Timed Countdown and Timed OHKO modes''')
parser.add_argument('--red_clock_time', default=defval(-2), type=int,
help='''Set amount of time, in minutes, to add from picking up red clocks; negative removes time instead''')
parser.add_argument('--blue_clock_time', default=defval(2), type=int,
help='''Set amount of time, in minutes, to add from picking up blue clocks; negative removes time instead''')
parser.add_argument('--green_clock_time', default=defval(4), type=int,
help='''Set amount of time, in minutes, to add from picking up green clocks; negative removes time instead''')
parser.add_argument('--dungeon_counters', default=defval('default'), const='default', nargs='?', choices=['default', 'on', 'pickup', 'off'],
help='''\
Select dungeon counter display settings. (default: %(default)s)
@@ -173,7 +181,7 @@ def parse_arguments(argv, no_defaults=False):
slightly biased to placing progression items with
less restrictions.
''')
parser.add_argument('--shuffle', default=defval('full'), const='full', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple'],
parser.add_argument('--shuffle', default=defval('vanilla'), const='vanilla', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple'],
help='''\
Select Entrance Shuffling Algorithm. (default: %(default)s)
Full: Mix cave and dungeon entrances freely while limiting
@@ -258,6 +266,8 @@ def parse_arguments(argv, no_defaults=False):
help='Specifies a list of items that will be in your starting inventory (separated by commas)')
parser.add_argument('--local_items', default=defval(''),
help='Specifies a list of items that will not spread across the multiworld (separated by commas)')
parser.add_argument('--non_local_items', default=defval(''),
help='Specifies a list of items that will spread across the multiworld (separated by commas)')
parser.add_argument('--custom', default=defval(False), help='Not supported.')
parser.add_argument('--customitemarray', default=defval(False), help='Not supported.')
parser.add_argument('--accessibility', default=defval('items'), const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\
@@ -283,8 +293,13 @@ def parse_arguments(argv, no_defaults=False):
''')
parser.add_argument('--heartcolor', default=defval('red'), const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow', 'random'],
help='Select the color of Link\'s heart meter. (default: %(default)s)')
parser.add_argument('--ow_palettes', default=defval('default'), choices=['default', 'random', 'blackout'])
parser.add_argument('--uw_palettes', default=defval('default'), choices=['default', 'random', 'blackout'])
parser.add_argument('--ow_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick'])
parser.add_argument('--uw_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick'])
parser.add_argument('--hud_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick'])
parser.add_argument('--shield_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick'])
parser.add_argument('--sword_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick'])
parser.add_argument('--link_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick'])
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,
@@ -334,12 +349,20 @@ def parse_arguments(argv, no_defaults=False):
create a binary patch file from which the randomized rom can be recreated using MultiClient.''')
parser.add_argument('--disable_glitch_boots', default=defval(False), action='store_true', help='''\
turns off starting with Pegasus Boots in glitched modes.''')
if multiargs.multi:
for player in range(1, multiargs.multi + 1):
parser.add_argument(f'--p{player}', default=defval(''), help=argparse.SUPPRESS)
ret = parser.parse_args(argv)
# shuffle medallions
ret.required_medallions = ("random", "random")
# cannot be set through CLI currently
ret.plando_items = []
ret.plando_texts = {}
ret.plando_connections = []
ret.glitch_boots = not ret.disable_glitch_boots
if ret.timer == "none":
ret.timer = False
@@ -361,14 +384,18 @@ def parse_arguments(argv, no_defaults=False):
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
'shuffle', 'crystals_ganon', 'crystals_gt', 'open_pyramid', 'timer',
'countdown_start_time', 'red_clock_time', 'blue_clock_time', 'green_clock_time',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
'local_items', 'retro', 'accessibility', 'hints', 'beemizer',
'local_items', 'non_local_items', 'retro', 'accessibility', 'hints', 'beemizer',
'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
'heartbeep', "skip_progression_balancing", "triforce_pieces_available",
"triforce_pieces_required", "shop_shuffle",
"triforce_pieces_required", "shop_shuffle", "required_medallions",
"plando_items", "plando_texts", "plando_connections",
'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic', 'restrict_dungeon_item_on_boss']:
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic',
'restrict_dungeon_item_on_boss',
'hud_palettes', 'sword_palettes', 'shield_palettes', 'link_palettes']:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1:
setattr(ret, name, {1: value})

View File

@@ -139,7 +139,7 @@ def link_entrances(world, player):
dw_must_exits = list(DW_Entrances_Must_Exit)
old_man_entrances = list(Old_Man_Entrances)
caves = list(Cave_Exits + Cave_Three_Exits)
single_doors = list(Single_Cave_Doors)
bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors + Bomb_Shop_Multi_Cave_Doors)
blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
door_targets = list(Single_Cave_Targets)
@@ -2240,6 +2240,20 @@ def unbias_some_entrances(world, Dungeon_Exits, Cave_Exits, Old_Man_House, Cave_
tuplize_lists_in_list(Cave_Three_Exits)
lookup = {
"both": connect_two_way,
"entrance": connect_entrance,
"exit": lambda x, y, z, w: connect_exit(x, z, y, w)
}
def plando_connect(world, player: int):
if world.plando_connections[player]:
for connection in world.plando_connections[player]:
func = lookup[connection.direction]
func(world, connection.entrance, connection.exit, player)
LW_Dungeon_Entrances = ['Desert Palace Entrance (South)',
'Desert Palace Entrance (West)',
'Desert Palace Entrance (North)',
@@ -2305,10 +2319,10 @@ Cave_Exits_Base = [['Elder House Exit (East)', 'Elder House Exit (West)'],
Cave_Exits_Base += [('Superbunny Cave Exit (Bottom)', 'Superbunny Cave Exit (Top)'),
('Spiral Cave Exit (Top)', 'Spiral Cave Exit')]
Cave_Three_Exits_Base = [('Spectacle Rock Cave Exit (Peak)', 'Spectacle Rock Cave Exit (Top)',
'Spectacle Rock Cave Exit'),
['Paradox Cave Exit (Top)', 'Paradox Cave Exit (Middle)','Paradox Cave Exit (Bottom)']]
Cave_Three_Exits_Base = [
('Spectacle Rock Cave Exit (Peak)', 'Spectacle Rock Cave Exit (Top)', 'Spectacle Rock Cave Exit'),
('Paradox Cave Exit (Top)', 'Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Bottom)')
]
LW_Entrances = ['Elder House (East)',

View File

@@ -320,8 +320,14 @@ def generate_itempool(world, player: int):
world.random.shuffle(nonprogressionitems)
# shuffle medallions
mm_medallion = world.random.choice(['Ether', 'Quake', 'Bombos'])
tr_medallion = world.random.choice(['Ether', 'Quake', 'Bombos'])
if world.required_medallions[player][0] == "random":
mm_medallion = world.random.choice(['Ether', 'Quake', 'Bombos'])
else:
mm_medallion = world.required_medallions[player][0]
if world.required_medallions[player][0] == "random":
tr_medallion = world.random.choice(['Ether', 'Quake', 'Bombos'])
else:
tr_medallion = world.required_medallions[player][0]
world.required_medallions[player] = (mm_medallion, tr_medallion)
place_bosses(world, player)

View File

@@ -225,3 +225,6 @@ for basename, substring in _simple_groups:
del (_simple_groups)
progression_items = {name for name, data in item_table.items() if type(data[3]) == int and data[0]}
item_name_groups['Everything'] = {name for name, data in item_table.items() if type(data[3]) == int}
item_name_groups['Progression Items'] = progression_items
item_name_groups['Non Progression Items'] = item_name_groups['Everything'] - progression_items

View File

@@ -71,6 +71,10 @@ def main(args, seed=None):
world.tile_shuffle = args.tile_shuffle.copy()
world.beemizer = args.beemizer.copy()
world.timer = args.timer.copy()
world.countdown_start_time = args.countdown_start_time.copy()
world.red_clock_time = args.red_clock_time.copy()
world.blue_clock_time = args.blue_clock_time.copy()
world.green_clock_time = args.green_clock_time.copy()
world.shufflepots = args.shufflepots.copy()
world.progressive = args.progressive.copy()
world.dungeon_counters = args.dungeon_counters.copy()
@@ -82,7 +86,11 @@ def main(args, seed=None):
world.shuffle_prizes = args.shuffle_prizes.copy()
world.sprite_pool = args.sprite_pool.copy()
world.dark_room_logic = args.dark_room_logic.copy()
world.plando_items = args.plando_items.copy()
world.plando_texts = args.plando_texts.copy()
world.plando_connections = args.plando_connections.copy()
world.restrict_dungeon_item_on_boss = args.restrict_dungeon_item_on_boss.copy()
world.required_medallions = args.required_medallions.copy()
world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)}
@@ -105,7 +113,30 @@ def main(args, seed=None):
item = ItemFactory(tok.strip(), player)
if item:
world.push_precollected(item)
world.local_items[player] = {item.strip() for item in args.local_items[player].split(',')}
# item in item_table gets checked in mystery, but not CLI - so we double-check here
world.local_items[player] = {item.strip() for item in args.local_items[player].split(',') if
item.strip() in item_table}
world.non_local_items[player] = {item.strip() for item in args.non_local_items[player].split(',') if
item.strip() in item_table}
# items can't be both local and non-local, prefer local
world.non_local_items[player] -= world.local_items[player]
# dungeon items can't be in non-local if the appropriate dungeon item shuffle setting is not set.
if not world.mapshuffle[player]:
world.non_local_items[player] -= item_name_groups['Maps']
if not world.compassshuffle[player]:
world.non_local_items[player] -= item_name_groups['Compasses']
if not world.keyshuffle[player]:
world.non_local_items[player] -= item_name_groups['Small Keys']
if not world.bigkeyshuffle[player]:
world.non_local_items[player] -= item_name_groups['Big Keys']
# Not possible to place pendants/crystals out side of boss prizes yet.
world.non_local_items[player] -= item_name_groups['Pendants']
world.non_local_items[player] -= item_name_groups['Crystals']
world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player], world.triforce_pieces_required[player])
@@ -129,6 +160,7 @@ def main(args, seed=None):
else:
link_inverted_entrances(world, player)
mark_dark_world_regions(world, player)
plando_connect(world, player)
logger.info('Generating Item Pool.')
@@ -144,11 +176,50 @@ def main(args, seed=None):
fill_prizes(world)
logger.info("Running Item Plando")
world_name_lookup = {world.player_names[player_id][0]: player_id for player_id in world.player_ids}
for player in world.player_ids:
placement: PlandoItem
for placement in world.plando_items[player]:
target_world: int = placement.world
if target_world is False or world.players == 1:
target_world = player # in own world
elif target_world is True: # in any other world
target_world = player
while target_world == player:
target_world = world.random.randint(1, world.players + 1)
elif target_world is None: # any random world
target_world = world.random.randint(1, world.players + 1)
elif type(target_world) == int: # target world by player id
pass
else: # find world by name
target_world = world_name_lookup[target_world]
location = world.get_location(placement.location, target_world)
if location.item:
raise Exception(f"Cannot place item into already filled location {location}.")
item = ItemFactory(placement.item, player)
if placement.from_pool:
try:
world.itempool.remove(item)
except ValueError:
logger.warning(f"Could not remove {item} from pool as it's already missing from it.")
if location.can_fill(world.state, item, False):
world.push_item(location, item, collect=False)
location.event = True # flag location to be checked during fill
location.locked = True
logger.debug(f"Plando placed {item} at {location}")
else:
raise Exception(f"Can't place {item} at {location} due to fill condition not met.")
logger.info('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 in ['balanced', 'vt26'] or any(
list(args.mapshuffle.values()) + list(args.compassshuffle.values()) +
list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())):
fill_dungeons_restrictive(world)
else:
fill_dungeons(world)
@@ -160,7 +231,7 @@ def main(args, seed=None):
elif args.algorithm == 'vt25':
distribute_items_restrictive(world, False)
elif args.algorithm == 'vt26':
distribute_items_restrictive(world, True, shuffled_locations)
distribute_items_restrictive(world, True)
elif args.algorithm == 'balanced':
distribute_items_restrictive(world, True)
@@ -177,7 +248,7 @@ def main(args, seed=None):
use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player]
or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
or world.shufflepots[player] or world.bush_shuffle[player]
or world.killable_thieves[player] or world.tile_shuffle[player])
or world.killable_thieves[player])
rom = LocalRom(args.rom)
@@ -187,13 +258,22 @@ def main(args, seed=None):
patch_enemizer(world, player, rom, args.enemizercli)
if args.race:
patch_race_rom(rom)
patch_race_rom(rom, world, player)
world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash)
palettes_options={}
palettes_options['dungeon']=args.uw_palettes[player]
palettes_options['overworld']=args.ow_palettes[player]
palettes_options['hud']=args.hud_palettes[player]
palettes_options['sword']=args.sword_palettes[player]
palettes_options['shield']=args.shield_palettes[player]
palettes_options['link']=args.link_palettes[player]
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], world, player, True)
palettes_options, world, player, True)
mcsb_name = ''
if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player],
@@ -287,6 +367,30 @@ def main(args, seed=None):
if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name:
er_hint_data[region.player][location.address] = main_entrance.name
ordered_areas = ('Light World', 'Dark World', 'Hyrule Castle', 'Agahnims Tower', 'Eastern Palace', 'Desert Palace',
'Tower of Hera', 'Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town', 'Ice Palace',
'Misery Mire', 'Turtle Rock', 'Ganons Tower', "Total")
checks_in_area = {player: {area: list() for area in ordered_areas}
for player in range(1, world.players + 1)}
for player in range(1, world.players + 1):
checks_in_area[player]["Total"] = 0
for location in [loc for loc in world.get_filled_locations() if type(loc.address) is int]:
main_entrance = get_entrance_to_region(location.parent_region)
if location.parent_region.dungeon:
dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower',
'Inverted Ganons Tower': 'Ganons Tower'}\
.get(location.parent_region.dungeon.name, location.parent_region.dungeon.name)
checks_in_area[location.player][dungeonname].append(location.address)
elif main_entrance.parent_region.type == RegionType.LightWorld:
checks_in_area[location.player]["Light World"].append(location.address)
elif main_entrance.parent_region.type == RegionType.DarkWorld:
checks_in_area[location.player]["Dark World"].append(location.address)
checks_in_area[location.player]["Total"] += 1
precollected_items = [[] for player in range(world.players)]
for item in world.precollected_items:
precollected_items[item.player - 1].append(item.code)
@@ -297,6 +401,7 @@ def main(args, seed=None):
for future in roms:
rom_name = future.result()
rom_names.append(rom_name)
minimum_versions = {"server": (1, 0, 0)}
multidata = zlib.compress(pickle.dumps({"names": parsed_names,
"roms": {base64.b64encode(rom_name).decode(): (team, slot) for slot, team, rom_name in rom_names},
"remote_items": {player for player in range(1, world.players + 1) if
@@ -306,11 +411,13 @@ def main(args, seed=None):
(location.item.code, location.item.player)
for location in world.get_filled_locations() if
type(location.address) is int},
"checks_in_area": checks_in_area,
"server_options": get_options()["server_options"],
"er_hint_data": er_hint_data,
"precollected_items": precollected_items,
"version": _version_tuple,
"tags": ["AP"]
"tags": ["AP"],
"minimum_versions": minimum_versions,
}), 9)
with open(output_path('%s.multidata' % outfilebase), 'wb') as f:

View File

@@ -1,4 +1,5 @@
import collections
import typing
from BaseClasses import Region, Location, Entrance, RegionType, Shop, TakeAny, UpgradeShop, ShopType
@@ -397,250 +398,315 @@ shop_table = {
'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)])
}
location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
'Bottle Merchant': (0x2eb18, 0x186339, False, 'with a merchant'),
'Flute Spot': (0x18014a, 0x18633d, False, 'underground'),
'Sunken Treasure': (0x180145, 0x186354, False, 'underwater'),
'Purple Chest': (0x33d68, 0x186359, False, 'from a box'),
"Blind's Hideout - Top": (0xeb0f, 0x1862e3, False, 'in a basement'),
"Blind's Hideout - Left": (0xeb12, 0x1862e6, False, 'in a basement'),
"Blind's Hideout - Right": (0xeb15, 0x1862e9, False, 'in a basement'),
"Blind's Hideout - Far Left": (0xeb18, 0x1862ec, False, 'in a basement'),
"Blind's Hideout - Far Right": (0xeb1b, 0x1862ef, False, 'in a basement'),
"Link's Uncle": (0x2df45, 0x18635f, False, 'with your uncle'),
'Secret Passage': (0xe971, 0x186145, False, 'near your uncle'),
'King Zora': (0xee1c3, 0x186360, False, 'at a high price'),
"Zora's Ledge": (0x180149, 0x186358, False, 'near Zora'),
'Waterfall Fairy - Left': (0xe9b0, 0x186184, False, 'near a fairy'),
'Waterfall Fairy - Right': (0xe9d1, 0x1861a5, False, 'near a fairy'),
"King's Tomb": (0xe97a, 0x18614e, False, 'alone in a cave'),
'Floodgate Chest': (0xe98c, 0x186160, False, 'in the dam'),
"Link's House": (0xe9bc, 0x186190, False, 'in your home'),
'Kakariko Tavern': (0xe9ce, 0x1861a2, False, 'in the bar'),
'Chicken House': (0xe9e9, 0x1861bd, False, 'near poultry'),
"Aginah's Cave": (0xe9f2, 0x1861c6, False, 'with Aginah'),
"Sahasrahla's Hut - Left": (0xea82, 0x186256, False, 'near the elder'),
"Sahasrahla's Hut - Middle": (0xea85, 0x186259, False, 'near the elder'),
"Sahasrahla's Hut - Right": (0xea88, 0x18625c, False, 'near the elder'),
'Sahasrahla': (0x2f1fc, 0x186365, False, 'with the elder'),
'Kakariko Well - Top': (0xea8e, 0x186262, False, 'in a well'),
'Kakariko Well - Left': (0xea91, 0x186265, False, 'in a well'),
'Kakariko Well - Middle': (0xea94, 0x186268, False, 'in a well'),
'Kakariko Well - Right': (0xea97, 0x18626b, False, 'in a well'),
'Kakariko Well - Bottom': (0xea9a, 0x18626e, False, 'in a well'),
'Blacksmith': (0x18002a, 0x186366, False, 'with the smith'),
'Magic Bat': (0x180015, 0x18635e, False, 'with the bat'),
'Sick Kid': (0x339cf, 0x186367, False, 'with the sick'),
'Hobo': (0x33e7d, 0x186368, False, 'with the hobo'),
'Lost Woods Hideout': (0x180000, 0x186348, False, 'near a thief'),
'Lumberjack Tree': (0x180001, 0x186349, False, 'in a hole'),
'Cave 45': (0x180003, 0x18634b, False, 'alone in a cave'),
'Graveyard Cave': (0x180004, 0x18634c, False, 'alone in a cave'),
'Checkerboard Cave': (0x180005, 0x18634d, False, 'alone in a cave'),
'Mini Moldorm Cave - Far Left': (0xeb42, 0x186316, False, 'near Moldorms'),
'Mini Moldorm Cave - Left': (0xeb45, 0x186319, False, 'near Moldorms'),
'Mini Moldorm Cave - Right': (0xeb48, 0x18631c, False, 'near Moldorms'),
'Mini Moldorm Cave - Far Right': (0xeb4b, 0x18631f, False, 'near Moldorms'),
'Mini Moldorm Cave - Generous Guy': (0x180010, 0x18635a, False, 'near Moldorms'),
'Ice Rod Cave': (0xeb4e, 0x186322, False, 'in a frozen cave'),
'Bonk Rock Cave': (0xeb3f, 0x186313, False, 'alone in a cave'),
'Library': (0x180012, 0x18635c, False, 'near books'),
'Potion Shop': (0x180014, 0x18635d, False, 'near potions'),
'Lake Hylia Island': (0x180144, 0x186353, False, 'on an island'),
'Maze Race': (0x180142, 0x186351, False, 'at the race'),
'Desert Ledge': (0x180143, 0x186352, False, 'in the desert'),
'Desert Palace - Big Chest': (0xe98f, 0x186163, False, 'in Desert Palace'),
'Desert Palace - Torch': (0x180160, 0x186362, False, 'in Desert Palace'),
'Desert Palace - Map Chest': (0xe9b6, 0x18618a, False, 'in Desert Palace'),
'Desert Palace - Compass Chest': (0xe9cb, 0x18619f, False, 'in Desert Palace'),
'Desert Palace - Big Key Chest': (0xe9c2, 0x186196, False, 'in Desert Palace'),
'Desert Palace - Boss': (0x180151, 0x18633f, False, 'with Lanmolas'),
'Eastern Palace - Compass Chest': (0xe977, 0x18614b, False, 'in Eastern Palace'),
'Eastern Palace - Big Chest': (0xe97d, 0x186151, False, 'in Eastern Palace'),
'Eastern Palace - Cannonball Chest': (0xe9b3, 0x186187, False, 'in Eastern Palace'),
'Eastern Palace - Big Key Chest': (0xe9b9, 0x18618d, False, 'in Eastern Palace'),
'Eastern Palace - Map Chest': (0xe9f5, 0x1861c9, False, 'in Eastern Palace'),
'Eastern Palace - Boss': (0x180150, 0x18633e, False, 'with the Armos'),
'Master Sword Pedestal': (0x289b0, 0x186369, False, 'at the pedestal'),
'Hyrule Castle - Boomerang Chest': (0xe974, 0x186148, False, 'in Hyrule Castle'),
'Hyrule Castle - Map Chest': (0xeb0c, 0x1862e0, False, 'in Hyrule Castle'),
"Hyrule Castle - Zelda's Chest": (0xeb09, 0x1862dd, False, 'in Hyrule Castle'),
'Sewers - Dark Cross': (0xe96e, 0x186142, False, 'in the sewers'),
'Sewers - Secret Room - Left': (0xeb5d, 0x186331, False, 'in the sewers'),
'Sewers - Secret Room - Middle': (0xeb60, 0x186334, False, 'in the sewers'),
'Sewers - Secret Room - Right': (0xeb63, 0x186337, False, 'in the sewers'),
'Sanctuary': (0xea79, 0x18624d, False, 'in Sanctuary'),
'Castle Tower - Room 03': (0xeab5, 0x186289, False, 'in Castle Tower'),
'Castle Tower - Dark Maze': (0xeab2, 0x186286, False, 'in Castle Tower'),
'Old Man': (0xf69fa, 0x186364, False, 'with the old man'),
'Spectacle Rock Cave': (0x180002, 0x18634a, False, 'alone in a cave'),
'Paradox Cave Lower - Far Left': (0xeb2a, 0x1862fe, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Left': (0xeb2d, 0x186301, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Right': (0xeb30, 0x186304, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Far Right': (0xeb33, 0x186307, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Middle': (0xeb36, 0x18630a, False, 'in a cave with seven chests'),
'Paradox Cave Upper - Left': (0xeb39, 0x18630d, False, 'in a cave with seven chests'),
'Paradox Cave Upper - Right': (0xeb3c, 0x186310, False, 'in a cave with seven chests'),
'Spiral Cave': (0xe9bf, 0x186193, False, 'in spiral cave'),
'Ether Tablet': (0x180016, 0x18633b, False, 'at a monolith'),
'Spectacle Rock': (0x180140, 0x18634f, False, 'atop a rock'),
'Tower of Hera - Basement Cage': (0x180162, 0x18633a, False, 'in Tower of Hera'),
'Tower of Hera - Map Chest': (0xe9ad, 0x186181, False, 'in Tower of Hera'),
'Tower of Hera - Big Key Chest': (0xe9e6, 0x1861ba, False, 'in Tower of Hera'),
'Tower of Hera - Compass Chest': (0xe9fb, 0x1861cf, False, 'in Tower of Hera'),
'Tower of Hera - Big Chest': (0xe9f8, 0x1861cc, False, 'in Tower of Hera'),
'Tower of Hera - Boss': (0x180152, 0x186340, False, 'with Moldorm'),
'Pyramid': (0x180147, 0x186356, False, 'on the pyramid'),
'Catfish': (0xee185, 0x186361, False, 'with a catfish'),
'Stumpy': (0x330c7, 0x18636a, False, 'with tree boy'),
'Digging Game': (0x180148, 0x186357, False, 'underground'),
'Bombos Tablet': (0x180017, 0x18633c, False, 'at a monolith'),
'Hype Cave - Top': (0xeb1e, 0x1862f2, False, 'near a bat-like man'),
'Hype Cave - Middle Right': (0xeb21, 0x1862f5, False, 'near a bat-like man'),
'Hype Cave - Middle Left': (0xeb24, 0x1862f8, False, 'near a bat-like man'),
'Hype Cave - Bottom': (0xeb27, 0x1862fb, False, 'near a bat-like man'),
'Hype Cave - Generous Guy': (0x180011, 0x18635b, False, 'with a bat-like man'),
'Peg Cave': (0x180006, 0x18634e, False, 'alone in a cave'),
'Pyramid Fairy - Left': (0xe980, 0x186154, False, 'near a fairy'),
'Pyramid Fairy - Right': (0xe983, 0x186157, False, 'near a fairy'),
'Brewery': (0xe9ec, 0x1861c0, False, 'alone in a home'),
'C-Shaped House': (0xe9ef, 0x1861c3, False, 'alone in a home'),
'Chest Game': (0xeda8, 0x18636b, False, 'as a prize'),
'Bumper Cave Ledge': (0x180146, 0x186355, False, 'on a ledge'),
'Mire Shed - Left': (0xea73, 0x186247, False, 'near sparks'),
'Mire Shed - Right': (0xea76, 0x18624a, False, 'near sparks'),
'Superbunny Cave - Top': (0xea7c, 0x186250, False, 'in a connection'),
'Superbunny Cave - Bottom': (0xea7f, 0x186253, False, 'in a connection'),
'Spike Cave': (0xea8b, 0x18625f, False, 'beyond spikes'),
'Hookshot Cave - Top Right': (0xeb51, 0x186325, False, 'across pits'),
'Hookshot Cave - Top Left': (0xeb54, 0x186328, False, 'across pits'),
'Hookshot Cave - Bottom Right': (0xeb5a, 0x18632e, False, 'across pits'),
'Hookshot Cave - Bottom Left': (0xeb57, 0x18632b, False, 'across pits'),
'Floating Island': (0x180141, 0x186350, False, 'on an island'),
'Mimic Cave': (0xe9c5, 0x186199, False, 'in a cave of mimicry'),
'Swamp Palace - Entrance': (0xea9d, 0x186271, False, 'in Swamp Palace'),
'Swamp Palace - Map Chest': (0xe986, 0x18615a, False, 'in Swamp Palace'),
'Swamp Palace - Big Chest': (0xe989, 0x18615d, False, 'in Swamp Palace'),
'Swamp Palace - Compass Chest': (0xeaa0, 0x186274, False, 'in Swamp Palace'),
'Swamp Palace - Big Key Chest': (0xeaa6, 0x18627a, False, 'in Swamp Palace'),
'Swamp Palace - West Chest': (0xeaa3, 0x186277, False, 'in Swamp Palace'),
'Swamp Palace - Flooded Room - Left': (0xeaa9, 0x18627d, False, 'in Swamp Palace'),
'Swamp Palace - Flooded Room - Right': (0xeaac, 0x186280, False, 'in Swamp Palace'),
'Swamp Palace - Waterfall Room': (0xeaaf, 0x186283, False, 'in Swamp Palace'),
'Swamp Palace - Boss': (0x180154, 0x186342, False, 'with Arrghus'),
"Thieves' Town - Big Key Chest": (0xea04, 0x1861d8, False, "in Thieves' Town"),
"Thieves' Town - Map Chest": (0xea01, 0x1861d5, False, "in Thieves' Town"),
"Thieves' Town - Compass Chest": (0xea07, 0x1861db, False, "in Thieves' Town"),
"Thieves' Town - Ambush Chest": (0xea0a, 0x1861de, False, "in Thieves' Town"),
"Thieves' Town - Attic": (0xea0d, 0x1861e1, False, "in Thieves' Town"),
"Thieves' Town - Big Chest": (0xea10, 0x1861e4, False, "in Thieves' Town"),
"Thieves' Town - Blind's Cell": (0xea13, 0x1861e7, False, "in Thieves' Town"),
"Thieves' Town - Boss": (0x180156, 0x186344, False, 'with Blind'),
'Skull Woods - Compass Chest': (0xe992, 0x186166, False, 'in Skull Woods'),
'Skull Woods - Map Chest': (0xe99b, 0x18616f, False, 'in Skull Woods'),
'Skull Woods - Big Chest': (0xe998, 0x18616c, False, 'in Skull Woods'),
'Skull Woods - Pot Prison': (0xe9a1, 0x186175, False, 'in Skull Woods'),
'Skull Woods - Pinball Room': (0xe9c8, 0x18619c, False, 'in Skull Woods'),
'Skull Woods - Big Key Chest': (0xe99e, 0x186172, False, 'in Skull Woods'),
'Skull Woods - Bridge Room': (0xe9fe, 0x1861d2, False, 'near Mothula'),
'Skull Woods - Boss': (0x180155, 0x186343, False, 'with Mothula'),
'Ice Palace - Compass Chest': (0xe9d4, 0x1861a8, False, 'in Ice Palace'),
'Ice Palace - Freezor Chest': (0xe995, 0x186169, False, 'in Ice Palace'),
'Ice Palace - Big Chest': (0xe9aa, 0x18617e, False, 'in Ice Palace'),
'Ice Palace - Iced T Room': (0xe9e3, 0x1861b7, False, 'in Ice Palace'),
'Ice Palace - Spike Room': (0xe9e0, 0x1861b4, False, 'in Ice Palace'),
'Ice Palace - Big Key Chest': (0xe9a4, 0x186178, False, 'in Ice Palace'),
'Ice Palace - Map Chest': (0xe9dd, 0x1861b1, False, 'in Ice Palace'),
'Ice Palace - Boss': (0x180157, 0x186345, False, 'with Kholdstare'),
'Misery Mire - Big Chest': (0xea67, 0x18623b, False, 'in Misery Mire'),
'Misery Mire - Map Chest': (0xea6a, 0x18623e, False, 'in Misery Mire'),
'Misery Mire - Main Lobby': (0xea5e, 0x186232, False, 'in Misery Mire'),
'Misery Mire - Bridge Chest': (0xea61, 0x186235, False, 'in Misery Mire'),
'Misery Mire - Spike Chest': (0xe9da, 0x1861ae, False, 'in Misery Mire'),
'Misery Mire - Compass Chest': (0xea64, 0x186238, False, 'in Misery Mire'),
'Misery Mire - Big Key Chest': (0xea6d, 0x186241, False, 'in Misery Mire'),
'Misery Mire - Boss': (0x180158, 0x186346, False, 'with Vitreous'),
'Turtle Rock - Compass Chest': (0xea22, 0x1861f6, False, 'in Turtle Rock'),
'Turtle Rock - Roller Room - Left': (0xea1c, 0x1861f0, False, 'in Turtle Rock'),
'Turtle Rock - Roller Room - Right': (0xea1f, 0x1861f3, False, 'in Turtle Rock'),
'Turtle Rock - Chain Chomps': (0xea16, 0x1861ea, False, 'in Turtle Rock'),
'Turtle Rock - Big Key Chest': (0xea25, 0x1861f9, False, 'in Turtle Rock'),
'Turtle Rock - Big Chest': (0xea19, 0x1861ed, False, 'in Turtle Rock'),
'Turtle Rock - Crystaroller Room': (0xea34, 0x186208, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Bottom Left': (0xea31, 0x186205, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Bottom Right': (0xea2e, 0x186202, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Top Left': (0xea2b, 0x1861ff, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Top Right': (0xea28, 0x1861fc, False, 'in Turtle Rock'),
'Turtle Rock - Boss': (0x180159, 0x186347, False, 'with Trinexx'),
'Palace of Darkness - Shooter Room': (0xea5b, 0x18622f, False, 'in Palace of Darkness'),
'Palace of Darkness - The Arena - Bridge': (0xea3d, 0x186211, False, 'in Palace of Darkness'),
'Palace of Darkness - Stalfos Basement': (0xea49, 0x18621d, False, 'in Palace of Darkness'),
'Palace of Darkness - Big Key Chest': (0xea37, 0x18620b, False, 'in Palace of Darkness'),
'Palace of Darkness - The Arena - Ledge': (0xea3a, 0x18620e, False, 'in Palace of Darkness'),
'Palace of Darkness - Map Chest': (0xea52, 0x186226, False, 'in Palace of Darkness'),
'Palace of Darkness - Compass Chest': (0xea43, 0x186217, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Basement - Left': (0xea4c, 0x186220, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Basement - Right': (0xea4f, 0x186223, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Maze - Top': (0xea55, 0x186229, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Maze - Bottom': (0xea58, 0x18622c, False, 'in Palace of Darkness'),
'Palace of Darkness - Big Chest': (0xea40, 0x186214, False, 'in Palace of Darkness'),
'Palace of Darkness - Harmless Hellway': (0xea46, 0x18621a, False, 'in Palace of Darkness'),
'Palace of Darkness - Boss': (0x180153, 0x186341, False, 'with Helmasaur King'),
"Ganons Tower - Bob's Torch": (0x180161, 0x186363, False, "in Ganon's Tower"),
'Ganons Tower - Hope Room - Left': (0xead9, 0x1862ad, False, "in Ganon's Tower"),
'Ganons Tower - Hope Room - Right': (0xeadc, 0x1862b0, False, "in Ganon's Tower"),
'Ganons Tower - Tile Room': (0xeae2, 0x1862b6, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Top Left': (0xeae5, 0x1862b9, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Top Right': (0xeae8, 0x1862bc, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Bottom Left': (0xeaeb, 0x1862bf, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Bottom Right': (0xeaee, 0x1862c2, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Top Left': (0xeab8, 0x18628c, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Top Right': (0xeabb, 0x18628f, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Bottom Left': (0xeabe, 0x186292, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Bottom Right': (0xeac1, 0x186295, False, "in Ganon's Tower"),
'Ganons Tower - Map Chest': (0xead3, 0x1862a7, False, "in Ganon's Tower"),
'Ganons Tower - Firesnake Room': (0xead0, 0x1862a4, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Top Left': (0xeac4, 0x186298, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Top Right': (0xeac7, 0x18629b, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Bottom Left': (0xeaca, 0x18629e, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Bottom Right': (0xeacd, 0x1862a1, False, "in Ganon's Tower"),
"Ganons Tower - Bob's Chest": (0xeadf, 0x1862b3, False, "in Ganon's Tower"),
'Ganons Tower - Big Chest': (0xead6, 0x1862aa, False, "in Ganon's Tower"),
'Ganons Tower - Big Key Room - Left': (0xeaf4, 0x1862c8, False, "in Ganon's Tower"),
'Ganons Tower - Big Key Room - Right': (0xeaf7, 0x1862cb, False, "in Ganon's Tower"),
'Ganons Tower - Big Key Chest': (0xeaf1, 0x1862c5, False, "in Ganon's Tower"),
'Ganons Tower - Mini Helmasaur Room - Left': (0xeafd, 0x1862d1, False, "atop Ganon's Tower"),
'Ganons Tower - Mini Helmasaur Room - Right': (0xeb00, 0x1862d4, False, "atop Ganon's Tower"),
'Ganons Tower - Pre-Moldorm Chest': (0xeb03, 0x1862d7, False, "atop Ganon's Tower"),
'Ganons Tower - Validation Chest': (0xeb06, 0x1862da, False, "atop Ganon's Tower"),
'Ganon': (None, None, False, 'from me'),
'Agahnim 1': (None, None, False, 'from Ganon\'s wizardry form'),
'Agahnim 2': (None, None, False, 'from Ganon\'s wizardry form'),
'Floodgate': (None, None, False, None),
'Frog': (None, None, False, None),
'Missing Smith': (None, None, False, None),
'Dark Blacksmith Ruins': (None, None, False, None),
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], None, True, 'Eastern Palace'),
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], None, True, 'Desert Palace'),
'Tower of Hera - Prize': (
[0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], None, True, 'Tower of Hera'),
'Palace of Darkness - Prize': (
[0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], None, True, 'Palace of Darkness'),
'Swamp Palace - Prize': (
[0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], None, True, 'Swamp Palace'),
'Thieves\' Town - Prize': (
[0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], None, True, 'Thieves\' Town'),
'Skull Woods - Prize': (
[0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], None, True, 'Skull Woods'),
'Ice Palace - Prize': (
[0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], None, True, 'Ice Palace'),
'Misery Mire - Prize': (
[0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], None, True, 'Misery Mire'),
'Turtle Rock - Prize': (
[0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
old_location_address_to_new_location_address = {
0x2eb18: 0x18001b, # Bottle Merchant
0x33d68: 0x18001a, # Purple Chest
0x2df45: 0x18001d, # Link's Uncle
0x2f1fc: 0x180008, # Sahasrahla
0x18002a: 0x18001c, # Black Smith
0x339cf: 0x180009, # Sick Kid
0x33e7d: 0x180019, # Hobo
0x180160: 0x18000b, # Desert Palace - Desert Torch
0x289b0: 0x180018, # Master Sword Pedestal
0xf69fa: 0x180007, # Old Man
0x180162: 0x18000d, # Tower of Hera - Basement Cage
0x330c7: 0x18000a, # Stumpy
0x180161: 0x18000c # Ganons Tower - Bob's Torch
}
key_drop_data = {
'Hyrule Castle - Map Guard Key Drop': [0x140036, 0x140037],
'Hyrule Castle - Boomerang Guard Key Drop': [0x140033, 0x140034],
'Hyrule Castle - Key Rat Key Drop': [0x14000c, 0x14000d],
'Hyrule Castle - Big Key Drop': [0x14003c, 0x14003d],
'Eastern Palace - Dark Square Pot Key': [0x14005a, 0x14005b],
'Eastern Palace - Dark Eyegore Key Drop': [0x140048, 0x140049],
'Desert Palace - Desert Tiles 1 Pot Key': [0x140030, 0x140031],
'Desert Palace - Beamos Hall Pot Key': [0x14002a, 0x14002b],
'Desert Palace - Desert Tiles 2 Pot Key': [0x140027, 0x140028],
'Castle Tower - Dark Archer Key Drop': [0x140060, 0x140061],
'Castle Tower - Circle of Pots Key Drop': [0x140051, 0x140052],
'Swamp Palace - Pot Row Pot Key': [0x140018, 0x140019],
'Swamp Palace - Trench 1 Pot Key': [0x140015, 0x140016],
'Swamp Palace - Hookshot Pot Key': [0x140012, 0x140013],
'Swamp Palace - Trench 2 Pot Key': [0x14000f, 0x140010],
'Swamp Palace - Waterway Pot Key': [0x140009, 0x14000a],
'Skull Woods - West Lobby Pot Key': [0x14002d, 0x14002e],
'Skull Woods - Spike Corner Key Drop': [0x14001b, 0x14001c],
'Thieves\' Town - Hallway Pot Key': [0x14005d, 0x14005e],
'Thieves\' Town - Spike Switch Pot Key': [0x14004e, 0x14004f],
'Ice Palace - Jelly Key Drop': [0x140003, 0x140004],
'Ice Palace - Conveyor Key Drop': [0x140021, 0x140022],
'Ice Palace - Hammer Block Key Drop': [0x140024, 0x140025],
'Ice Palace - Many Pots Pot Key': [0x140045, 0x140046],
'Misery Mire - Spikes Pot Key': [0x140054, 0x140055],
'Misery Mire - Fishbone Pot Key': [0x14004b, 0x14004c],
'Misery Mire - Conveyor Crystal Key Drop': [0x140063, 0x140064],
'Turtle Rock - Pokey 1 Key Drop': [0x140057, 0x140058],
'Turtle Rock - Pokey 2 Key Drop': [0x140006, 0x140007],
'Ganons Tower - Conveyor Cross Pot Key': [0x14003f, 0x140040],
'Ganons Tower - Double Switch Pot Key': [0x140042, 0x140043],
'Ganons Tower - Conveyor Star Pits Pot Key': [0x140039, 0x14003a],
'Ganons Tower - Mini Helmasaur Key Drop': [0x14001e, 0x14001f]
}
# tuple contents:
# address to write to for item
# address to write to for player getting the item
# can this location drop a crystal
# hint tile/npc text for this location
location_table: typing.Dict[str,
typing.Tuple[typing.Optional[typing.Union[int, typing.List[int]]],
typing.Optional[int],
bool,
typing.Optional[str]]] = \
{'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
'Bottle Merchant': (0x2eb18, 0x186339, False, 'with a merchant'),
'Flute Spot': (0x18014a, 0x18633d, False, 'underground'),
'Sunken Treasure': (0x180145, 0x186354, False, 'underwater'),
'Purple Chest': (0x33d68, 0x186359, False, 'from a box'),
"Blind's Hideout - Top": (0xeb0f, 0x1862e3, False, 'in a basement'),
"Blind's Hideout - Left": (0xeb12, 0x1862e6, False, 'in a basement'),
"Blind's Hideout - Right": (0xeb15, 0x1862e9, False, 'in a basement'),
"Blind's Hideout - Far Left": (0xeb18, 0x1862ec, False, 'in a basement'),
"Blind's Hideout - Far Right": (0xeb1b, 0x1862ef, False, 'in a basement'),
"Link's Uncle": (0x2df45, 0x18635f, False, 'with your uncle'),
'Secret Passage': (0xe971, 0x186145, False, 'near your uncle'),
'King Zora': (0xee1c3, 0x186360, False, 'at a high price'),
"Zora's Ledge": (0x180149, 0x186358, False, 'near Zora'),
'Waterfall Fairy - Left': (0xe9b0, 0x186184, False, 'near a fairy'),
'Waterfall Fairy - Right': (0xe9d1, 0x1861a5, False, 'near a fairy'),
"King's Tomb": (0xe97a, 0x18614e, False, 'alone in a cave'),
'Floodgate Chest': (0xe98c, 0x186160, False, 'in the dam'),
"Link's House": (0xe9bc, 0x186190, False, 'in your home'),
'Kakariko Tavern': (0xe9ce, 0x1861a2, False, 'in the bar'),
'Chicken House': (0xe9e9, 0x1861bd, False, 'near poultry'),
"Aginah's Cave": (0xe9f2, 0x1861c6, False, 'with Aginah'),
"Sahasrahla's Hut - Left": (0xea82, 0x186256, False, 'near the elder'),
"Sahasrahla's Hut - Middle": (0xea85, 0x186259, False, 'near the elder'),
"Sahasrahla's Hut - Right": (0xea88, 0x18625c, False, 'near the elder'),
'Sahasrahla': (0x2f1fc, 0x186365, False, 'with the elder'),
'Kakariko Well - Top': (0xea8e, 0x186262, False, 'in a well'),
'Kakariko Well - Left': (0xea91, 0x186265, False, 'in a well'),
'Kakariko Well - Middle': (0xea94, 0x186268, False, 'in a well'),
'Kakariko Well - Right': (0xea97, 0x18626b, False, 'in a well'),
'Kakariko Well - Bottom': (0xea9a, 0x18626e, False, 'in a well'),
'Blacksmith': (0x18002a, 0x186366, False, 'with the smith'),
'Magic Bat': (0x180015, 0x18635e, False, 'with the bat'),
'Sick Kid': (0x339cf, 0x186367, False, 'with the sick'),
'Hobo': (0x33e7d, 0x186368, False, 'with the hobo'),
'Lost Woods Hideout': (0x180000, 0x186348, False, 'near a thief'),
'Lumberjack Tree': (0x180001, 0x186349, False, 'in a hole'),
'Cave 45': (0x180003, 0x18634b, False, 'alone in a cave'),
'Graveyard Cave': (0x180004, 0x18634c, False, 'alone in a cave'),
'Checkerboard Cave': (0x180005, 0x18634d, False, 'alone in a cave'),
'Mini Moldorm Cave - Far Left': (0xeb42, 0x186316, False, 'near Moldorms'),
'Mini Moldorm Cave - Left': (0xeb45, 0x186319, False, 'near Moldorms'),
'Mini Moldorm Cave - Right': (0xeb48, 0x18631c, False, 'near Moldorms'),
'Mini Moldorm Cave - Far Right': (0xeb4b, 0x18631f, False, 'near Moldorms'),
'Mini Moldorm Cave - Generous Guy': (0x180010, 0x18635a, False, 'near Moldorms'),
'Ice Rod Cave': (0xeb4e, 0x186322, False, 'in a frozen cave'),
'Bonk Rock Cave': (0xeb3f, 0x186313, False, 'alone in a cave'),
'Library': (0x180012, 0x18635c, False, 'near books'),
'Potion Shop': (0x180014, 0x18635d, False, 'near potions'),
'Lake Hylia Island': (0x180144, 0x186353, False, 'on an island'),
'Maze Race': (0x180142, 0x186351, False, 'at the race'),
'Desert Ledge': (0x180143, 0x186352, False, 'in the desert'),
'Desert Palace - Big Chest': (0xe98f, 0x186163, False, 'in Desert Palace'),
'Desert Palace - Torch': (0x180160, 0x186362, False, 'in Desert Palace'),
'Desert Palace - Map Chest': (0xe9b6, 0x18618a, False, 'in Desert Palace'),
'Desert Palace - Compass Chest': (0xe9cb, 0x18619f, False, 'in Desert Palace'),
'Desert Palace - Big Key Chest': (0xe9c2, 0x186196, False, 'in Desert Palace'),
'Desert Palace - Boss': (0x180151, 0x18633f, False, 'with Lanmolas'),
'Eastern Palace - Compass Chest': (0xe977, 0x18614b, False, 'in Eastern Palace'),
'Eastern Palace - Big Chest': (0xe97d, 0x186151, False, 'in Eastern Palace'),
'Eastern Palace - Cannonball Chest': (0xe9b3, 0x186187, False, 'in Eastern Palace'),
'Eastern Palace - Big Key Chest': (0xe9b9, 0x18618d, False, 'in Eastern Palace'),
'Eastern Palace - Map Chest': (0xe9f5, 0x1861c9, False, 'in Eastern Palace'),
'Eastern Palace - Boss': (0x180150, 0x18633e, False, 'with the Armos'),
'Master Sword Pedestal': (0x289b0, 0x186369, False, 'at the pedestal'),
'Hyrule Castle - Boomerang Chest': (0xe974, 0x186148, False, 'in Hyrule Castle'),
'Hyrule Castle - Map Chest': (0xeb0c, 0x1862e0, False, 'in Hyrule Castle'),
"Hyrule Castle - Zelda's Chest": (0xeb09, 0x1862dd, False, 'in Hyrule Castle'),
'Sewers - Dark Cross': (0xe96e, 0x186142, False, 'in the sewers'),
'Sewers - Secret Room - Left': (0xeb5d, 0x186331, False, 'in the sewers'),
'Sewers - Secret Room - Middle': (0xeb60, 0x186334, False, 'in the sewers'),
'Sewers - Secret Room - Right': (0xeb63, 0x186337, False, 'in the sewers'),
'Sanctuary': (0xea79, 0x18624d, False, 'in Sanctuary'),
'Castle Tower - Room 03': (0xeab5, 0x186289, False, 'in Castle Tower'),
'Castle Tower - Dark Maze': (0xeab2, 0x186286, False, 'in Castle Tower'),
'Old Man': (0xf69fa, 0x186364, False, 'with the old man'),
'Spectacle Rock Cave': (0x180002, 0x18634a, False, 'alone in a cave'),
'Paradox Cave Lower - Far Left': (0xeb2a, 0x1862fe, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Left': (0xeb2d, 0x186301, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Right': (0xeb30, 0x186304, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Far Right': (0xeb33, 0x186307, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Middle': (0xeb36, 0x18630a, False, 'in a cave with seven chests'),
'Paradox Cave Upper - Left': (0xeb39, 0x18630d, False, 'in a cave with seven chests'),
'Paradox Cave Upper - Right': (0xeb3c, 0x186310, False, 'in a cave with seven chests'),
'Spiral Cave': (0xe9bf, 0x186193, False, 'in spiral cave'),
'Ether Tablet': (0x180016, 0x18633b, False, 'at a monolith'),
'Spectacle Rock': (0x180140, 0x18634f, False, 'atop a rock'),
'Tower of Hera - Basement Cage': (0x180162, 0x18633a, False, 'in Tower of Hera'),
'Tower of Hera - Map Chest': (0xe9ad, 0x186181, False, 'in Tower of Hera'),
'Tower of Hera - Big Key Chest': (0xe9e6, 0x1861ba, False, 'in Tower of Hera'),
'Tower of Hera - Compass Chest': (0xe9fb, 0x1861cf, False, 'in Tower of Hera'),
'Tower of Hera - Big Chest': (0xe9f8, 0x1861cc, False, 'in Tower of Hera'),
'Tower of Hera - Boss': (0x180152, 0x186340, False, 'with Moldorm'),
'Pyramid': (0x180147, 0x186356, False, 'on the pyramid'),
'Catfish': (0xee185, 0x186361, False, 'with a catfish'),
'Stumpy': (0x330c7, 0x18636a, False, 'with tree boy'),
'Digging Game': (0x180148, 0x186357, False, 'underground'),
'Bombos Tablet': (0x180017, 0x18633c, False, 'at a monolith'),
'Hype Cave - Top': (0xeb1e, 0x1862f2, False, 'near a bat-like man'),
'Hype Cave - Middle Right': (0xeb21, 0x1862f5, False, 'near a bat-like man'),
'Hype Cave - Middle Left': (0xeb24, 0x1862f8, False, 'near a bat-like man'),
'Hype Cave - Bottom': (0xeb27, 0x1862fb, False, 'near a bat-like man'),
'Hype Cave - Generous Guy': (0x180011, 0x18635b, False, 'with a bat-like man'),
'Peg Cave': (0x180006, 0x18634e, False, 'alone in a cave'),
'Pyramid Fairy - Left': (0xe980, 0x186154, False, 'near a fairy'),
'Pyramid Fairy - Right': (0xe983, 0x186157, False, 'near a fairy'),
'Brewery': (0xe9ec, 0x1861c0, False, 'alone in a home'),
'C-Shaped House': (0xe9ef, 0x1861c3, False, 'alone in a home'),
'Chest Game': (0xeda8, 0x18636b, False, 'as a prize'),
'Bumper Cave Ledge': (0x180146, 0x186355, False, 'on a ledge'),
'Mire Shed - Left': (0xea73, 0x186247, False, 'near sparks'),
'Mire Shed - Right': (0xea76, 0x18624a, False, 'near sparks'),
'Superbunny Cave - Top': (0xea7c, 0x186250, False, 'in a connection'),
'Superbunny Cave - Bottom': (0xea7f, 0x186253, False, 'in a connection'),
'Spike Cave': (0xea8b, 0x18625f, False, 'beyond spikes'),
'Hookshot Cave - Top Right': (0xeb51, 0x186325, False, 'across pits'),
'Hookshot Cave - Top Left': (0xeb54, 0x186328, False, 'across pits'),
'Hookshot Cave - Bottom Right': (0xeb5a, 0x18632e, False, 'across pits'),
'Hookshot Cave - Bottom Left': (0xeb57, 0x18632b, False, 'across pits'),
'Floating Island': (0x180141, 0x186350, False, 'on an island'),
'Mimic Cave': (0xe9c5, 0x186199, False, 'in a cave of mimicry'),
'Swamp Palace - Entrance': (0xea9d, 0x186271, False, 'in Swamp Palace'),
'Swamp Palace - Map Chest': (0xe986, 0x18615a, False, 'in Swamp Palace'),
'Swamp Palace - Big Chest': (0xe989, 0x18615d, False, 'in Swamp Palace'),
'Swamp Palace - Compass Chest': (0xeaa0, 0x186274, False, 'in Swamp Palace'),
'Swamp Palace - Big Key Chest': (0xeaa6, 0x18627a, False, 'in Swamp Palace'),
'Swamp Palace - West Chest': (0xeaa3, 0x186277, False, 'in Swamp Palace'),
'Swamp Palace - Flooded Room - Left': (0xeaa9, 0x18627d, False, 'in Swamp Palace'),
'Swamp Palace - Flooded Room - Right': (0xeaac, 0x186280, False, 'in Swamp Palace'),
'Swamp Palace - Waterfall Room': (0xeaaf, 0x186283, False, 'in Swamp Palace'),
'Swamp Palace - Boss': (0x180154, 0x186342, False, 'with Arrghus'),
"Thieves' Town - Big Key Chest": (0xea04, 0x1861d8, False, "in Thieves' Town"),
"Thieves' Town - Map Chest": (0xea01, 0x1861d5, False, "in Thieves' Town"),
"Thieves' Town - Compass Chest": (0xea07, 0x1861db, False, "in Thieves' Town"),
"Thieves' Town - Ambush Chest": (0xea0a, 0x1861de, False, "in Thieves' Town"),
"Thieves' Town - Attic": (0xea0d, 0x1861e1, False, "in Thieves' Town"),
"Thieves' Town - Big Chest": (0xea10, 0x1861e4, False, "in Thieves' Town"),
"Thieves' Town - Blind's Cell": (0xea13, 0x1861e7, False, "in Thieves' Town"),
"Thieves' Town - Boss": (0x180156, 0x186344, False, 'with Blind'),
'Skull Woods - Compass Chest': (0xe992, 0x186166, False, 'in Skull Woods'),
'Skull Woods - Map Chest': (0xe99b, 0x18616f, False, 'in Skull Woods'),
'Skull Woods - Big Chest': (0xe998, 0x18616c, False, 'in Skull Woods'),
'Skull Woods - Pot Prison': (0xe9a1, 0x186175, False, 'in Skull Woods'),
'Skull Woods - Pinball Room': (0xe9c8, 0x18619c, False, 'in Skull Woods'),
'Skull Woods - Big Key Chest': (0xe99e, 0x186172, False, 'in Skull Woods'),
'Skull Woods - Bridge Room': (0xe9fe, 0x1861d2, False, 'near Mothula'),
'Skull Woods - Boss': (0x180155, 0x186343, False, 'with Mothula'),
'Ice Palace - Compass Chest': (0xe9d4, 0x1861a8, False, 'in Ice Palace'),
'Ice Palace - Freezor Chest': (0xe995, 0x186169, False, 'in Ice Palace'),
'Ice Palace - Big Chest': (0xe9aa, 0x18617e, False, 'in Ice Palace'),
'Ice Palace - Iced T Room': (0xe9e3, 0x1861b7, False, 'in Ice Palace'),
'Ice Palace - Spike Room': (0xe9e0, 0x1861b4, False, 'in Ice Palace'),
'Ice Palace - Big Key Chest': (0xe9a4, 0x186178, False, 'in Ice Palace'),
'Ice Palace - Map Chest': (0xe9dd, 0x1861b1, False, 'in Ice Palace'),
'Ice Palace - Boss': (0x180157, 0x186345, False, 'with Kholdstare'),
'Misery Mire - Big Chest': (0xea67, 0x18623b, False, 'in Misery Mire'),
'Misery Mire - Map Chest': (0xea6a, 0x18623e, False, 'in Misery Mire'),
'Misery Mire - Main Lobby': (0xea5e, 0x186232, False, 'in Misery Mire'),
'Misery Mire - Bridge Chest': (0xea61, 0x186235, False, 'in Misery Mire'),
'Misery Mire - Spike Chest': (0xe9da, 0x1861ae, False, 'in Misery Mire'),
'Misery Mire - Compass Chest': (0xea64, 0x186238, False, 'in Misery Mire'),
'Misery Mire - Big Key Chest': (0xea6d, 0x186241, False, 'in Misery Mire'),
'Misery Mire - Boss': (0x180158, 0x186346, False, 'with Vitreous'),
'Turtle Rock - Compass Chest': (0xea22, 0x1861f6, False, 'in Turtle Rock'),
'Turtle Rock - Roller Room - Left': (0xea1c, 0x1861f0, False, 'in Turtle Rock'),
'Turtle Rock - Roller Room - Right': (0xea1f, 0x1861f3, False, 'in Turtle Rock'),
'Turtle Rock - Chain Chomps': (0xea16, 0x1861ea, False, 'in Turtle Rock'),
'Turtle Rock - Big Key Chest': (0xea25, 0x1861f9, False, 'in Turtle Rock'),
'Turtle Rock - Big Chest': (0xea19, 0x1861ed, False, 'in Turtle Rock'),
'Turtle Rock - Crystaroller Room': (0xea34, 0x186208, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Bottom Left': (0xea31, 0x186205, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Bottom Right': (0xea2e, 0x186202, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Top Left': (0xea2b, 0x1861ff, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Top Right': (0xea28, 0x1861fc, False, 'in Turtle Rock'),
'Turtle Rock - Boss': (0x180159, 0x186347, False, 'with Trinexx'),
'Palace of Darkness - Shooter Room': (0xea5b, 0x18622f, False, 'in Palace of Darkness'),
'Palace of Darkness - The Arena - Bridge': (0xea3d, 0x186211, False, 'in Palace of Darkness'),
'Palace of Darkness - Stalfos Basement': (0xea49, 0x18621d, False, 'in Palace of Darkness'),
'Palace of Darkness - Big Key Chest': (0xea37, 0x18620b, False, 'in Palace of Darkness'),
'Palace of Darkness - The Arena - Ledge': (0xea3a, 0x18620e, False, 'in Palace of Darkness'),
'Palace of Darkness - Map Chest': (0xea52, 0x186226, False, 'in Palace of Darkness'),
'Palace of Darkness - Compass Chest': (0xea43, 0x186217, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Basement - Left': (0xea4c, 0x186220, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Basement - Right': (0xea4f, 0x186223, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Maze - Top': (0xea55, 0x186229, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Maze - Bottom': (0xea58, 0x18622c, False, 'in Palace of Darkness'),
'Palace of Darkness - Big Chest': (0xea40, 0x186214, False, 'in Palace of Darkness'),
'Palace of Darkness - Harmless Hellway': (0xea46, 0x18621a, False, 'in Palace of Darkness'),
'Palace of Darkness - Boss': (0x180153, 0x186341, False, 'with Helmasaur King'),
"Ganons Tower - Bob's Torch": (0x180161, 0x186363, False, "in Ganon's Tower"),
'Ganons Tower - Hope Room - Left': (0xead9, 0x1862ad, False, "in Ganon's Tower"),
'Ganons Tower - Hope Room - Right': (0xeadc, 0x1862b0, False, "in Ganon's Tower"),
'Ganons Tower - Tile Room': (0xeae2, 0x1862b6, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Top Left': (0xeae5, 0x1862b9, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Top Right': (0xeae8, 0x1862bc, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Bottom Left': (0xeaeb, 0x1862bf, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Bottom Right': (0xeaee, 0x1862c2, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Top Left': (0xeab8, 0x18628c, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Top Right': (0xeabb, 0x18628f, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Bottom Left': (0xeabe, 0x186292, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Bottom Right': (0xeac1, 0x186295, False, "in Ganon's Tower"),
'Ganons Tower - Map Chest': (0xead3, 0x1862a7, False, "in Ganon's Tower"),
'Ganons Tower - Firesnake Room': (0xead0, 0x1862a4, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Top Left': (0xeac4, 0x186298, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Top Right': (0xeac7, 0x18629b, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Bottom Left': (0xeaca, 0x18629e, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Bottom Right': (0xeacd, 0x1862a1, False, "in Ganon's Tower"),
"Ganons Tower - Bob's Chest": (0xeadf, 0x1862b3, False, "in Ganon's Tower"),
'Ganons Tower - Big Chest': (0xead6, 0x1862aa, False, "in Ganon's Tower"),
'Ganons Tower - Big Key Room - Left': (0xeaf4, 0x1862c8, False, "in Ganon's Tower"),
'Ganons Tower - Big Key Room - Right': (0xeaf7, 0x1862cb, False, "in Ganon's Tower"),
'Ganons Tower - Big Key Chest': (0xeaf1, 0x1862c5, False, "in Ganon's Tower"),
'Ganons Tower - Mini Helmasaur Room - Left': (0xeafd, 0x1862d1, False, "atop Ganon's Tower"),
'Ganons Tower - Mini Helmasaur Room - Right': (0xeb00, 0x1862d4, False, "atop Ganon's Tower"),
'Ganons Tower - Pre-Moldorm Chest': (0xeb03, 0x1862d7, False, "atop Ganon's Tower"),
'Ganons Tower - Validation Chest': (0xeb06, 0x1862da, False, "atop Ganon's Tower"),
'Ganon': (None, None, False, 'from me'),
'Agahnim 1': (None, None, False, 'from Ganon\'s wizardry form'),
'Agahnim 2': (None, None, False, 'from Ganon\'s wizardry form'),
'Floodgate': (None, None, False, None),
'Frog': (None, None, False, None),
'Missing Smith': (None, None, False, None),
'Dark Blacksmith Ruins': (None, None, False, None),
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], None, True, 'Eastern Palace'),
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], None, True, 'Desert Palace'),
'Tower of Hera - Prize': (
[0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], None, True, 'Tower of Hera'),
'Palace of Darkness - Prize': (
[0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], None, True, 'Palace of Darkness'),
'Swamp Palace - Prize': (
[0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], None, True, 'Swamp Palace'),
'Thieves\' Town - Prize': (
[0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], None, True, 'Thieves\' Town'),
'Skull Woods - Prize': (
[0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], None, True, 'Skull Woods'),
'Ice Palace - Prize': (
[0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], None, True, 'Ice Palace'),
'Misery Mire - Prize': (
[0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], None, True, 'Misery Mire'),
'Turtle Rock - Prize': (
[0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
lookup_id_to_name[-1] = "cheat console"
lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}, -1: "cheat console"}
lookup_name_to_id = {name: data[0] for name, data in location_table.items() if type(data[0]) == int}
lookup_name_to_id = {**lookup_name_to_id, **{name: data[1] for name, data in key_drop_data.items()}, "cheat console": -1}
lookup_vanilla_location_to_entrance = {1572883: 'Kings Grave Inner Rocks', 191256: 'Kings Grave Inner Rocks',
1573194: 'Kings Grave Inner Rocks', 1573189: 'Kings Grave Inner Rocks',
@@ -745,7 +811,28 @@ lookup_vanilla_location_to_entrance = {1572883: 'Kings Grave Inner Rocks', 19125
60103: 'Ganons Tower', 60106: 'Ganons Tower', 60109: 'Ganons Tower',
60127: 'Ganons Tower', 60118: 'Ganons Tower', 60148: 'Ganons Tower',
60151: 'Ganons Tower', 60145: 'Ganons Tower', 60157: 'Ganons Tower',
60160: 'Ganons Tower', 60163: 'Ganons Tower', 60166: 'Ganons Tower'}
60160: 'Ganons Tower', 60163: 'Ganons Tower', 60166: 'Ganons Tower',
0x140037: 'Hyrule Castle Entrance (South)',
0x140034: 'Hyrule Castle Entrance (South)',
0x14000d: 'Hyrule Castle Entrance (South)',
0x14003d: 'Hyrule Castle Entrance (South)',
0x14005b: 'Eastern Palace', 0x140049: 'Eastern Palace',
0x140031: 'Desert Palace Entrance (North)',
0x14002b: 'Desert Palace Entrance (North)',
0x140028: 'Desert Palace Entrance (North)',
0x140061: 'Agahnims Tower', 0x140052: 'Agahnims Tower',
0x140019: 'Swamp Palace', 0x140016: 'Swamp Palace', 0x140013: 'Swamp Palace',
0x140010: 'Swamp Palace', 0x14000a: 'Swamp Palace',
0x14002e: 'Skull Woods Second Section Door (East)',
0x14001c: 'Skull Woods Final Section',
0x14005e: 'Thieves Town', 0x14004f: 'Thieves Town',
0x140004: 'Ice Palace', 0x140022: 'Ice Palace',
0x140025: 'Ice Palace', 0x140046: 'Ice Palace',
0x140055: 'Misery Mire', 0x14004c: 'Misery Mire',
0x140064: 'Misery Mire',
0x140058: 'Turtle Rock', 0x140007: 'Dark Death Mountain Ledge (West)',
0x140040: 'Ganons Tower', 0x140043: 'Ganons Tower',
0x14003a: 'Ganons Tower', 0x14001f: 'Ganons Tower'}
lookup_prizes = {location for location in location_table if location.endswith(" - Prize")}
lookup_boss_drops = {location for location in location_table if location.endswith(" - Boss")}

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ def set_rules(world, player):
if world.players > 1:
locality_rules(world, player)
if world.logic[player] == 'nologic':
logging.getLogger('').info(
logging.info(
'WARNING! Seeds generated under this logic often require major glitches and may be impossible!')
if world.players == 1:
world.get_region('Menu', player).can_reach_private = lambda state: True
@@ -38,7 +38,7 @@ def set_rules(world, player):
open_rules(world, player)
inverted_rules(world, player)
else:
raise NotImplementedError('Not implemented yet')
raise NotImplementedError(f'World state {world.mode[player]} is not implemented yet')
if world.logic[player] == 'noglitches':
no_glitches_rules(world, player)
@@ -104,7 +104,7 @@ def mirrorless_path_to_castle_courtyard(world, player):
else:
queue.append((entrance.connected_region, new_path))
raise Exception(f"Could not find mirrorless path to castle courtyard for Player {player}")
raise Exception(f"Could not find mirrorless path to castle courtyard for Player {player} ({world.get_player_names(player)})")
def set_rule(spot, rule):
spot.access_rule = rule
@@ -179,6 +179,10 @@ def locality_rules(world, player):
for location in world.get_locations():
if location.player != player:
forbid_items_for_player(location, world.local_items[player], player)
if world.non_local_items[player]:
for location in world.get_locations():
if location.player == player:
forbid_items_for_player(location, world.non_local_items[player], player)
non_crossover_items = (item_name_groups["Small Keys"] | item_name_groups["Big Keys"] | progression_items) - {
@@ -429,6 +433,7 @@ def global_rules(world, player):
def default_rules(world, player):
"""Default world rules when world state is not inverted."""
# overworld requirements
set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has_Boots(player))
set_rule(world.get_entrance('Kings Grave Outer Rocks', player), lambda state: state.can_lift_heavy_rocks(player))
@@ -444,9 +449,9 @@ def default_rules(world, player):
set_rule(world.get_entrance('50 Rupee Cave', player), lambda state: state.can_lift_rocks(player))
set_rule(world.get_entrance('Death Mountain Entrance Rock', player), lambda state: state.can_lift_rocks(player))
set_rule(world.get_entrance('Bumper Cave Entrance Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Flute Spot 1', player), lambda state: state.has('Flute', player))
set_rule(world.get_entrance('Flute Spot 1', player), lambda state: state.can_flute(player))
set_rule(world.get_entrance('Lake Hylia Central Island Teleporter', player), lambda state: state.can_lift_heavy_rocks(player))
set_rule(world.get_entrance('Dark Desert Teleporter', player), lambda state: state.has('Flute', player) and state.can_lift_heavy_rocks(player))
set_rule(world.get_entrance('Dark Desert Teleporter', player), lambda state: state.can_flute(player) and state.can_lift_heavy_rocks(player))
set_rule(world.get_entrance('East Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer
set_rule(world.get_entrance('South Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer
set_rule(world.get_entrance('Kakariko Teleporter', player), lambda state: ((state.has('Hammer', player) and state.can_lift_rocks(player)) or state.can_lift_heavy_rocks(player)) and state.has_Pearl(player)) # bunny cannot lift bushes
@@ -500,7 +505,7 @@ def default_rules(world, player):
set_rule(world.get_entrance('East Dark World Bridge', player), lambda state: state.has_Pearl(player) and state.has('Hammer', player))
set_rule(world.get_entrance('Lake Hylia Island Mirror Spot', player), lambda state: state.has_Pearl(player) and state.has_Mirror(player) and state.has('Flippers', player))
set_rule(world.get_entrance('Lake Hylia Central Island Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # ToDo any fake flipper set up?
set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))
set_rule(world.get_entrance('Graveyard Ledge Mirror Spot', player), lambda state: state.has_Pearl(player) and state.has_Mirror(player))
set_rule(world.get_entrance('Bumper Cave Entrance Rock', player), lambda state: state.has_Pearl(player) and state.can_lift_rocks(player))
set_rule(world.get_entrance('Bumper Cave Ledge Mirror Spot', player), lambda state: state.has_Mirror(player))
@@ -626,7 +631,7 @@ def inverted_rules(world, player):
set_rule(world.get_entrance('Village of Outcasts Heavy Rock', player), lambda state: state.can_lift_heavy_rocks(player))
set_rule(world.get_entrance('East Dark World Bridge', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('Lake Hylia Central Island Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has('Flippers', player)) # ToDo any fake flipper set up? (Qirn Jump)
set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has('Flippers', player))
set_rule(world.get_entrance('Bumper Cave Entrance Rock', player), lambda state: state.can_lift_rocks(player))
set_rule(world.get_entrance('Bumper Cave Ledge Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Hammer Peg Area Mirror Spot', player), lambda state: state.has_Mirror(player))
@@ -690,14 +695,8 @@ def inverted_rules(world, player):
swordless_rules(world, player)
def no_glitches_rules(world, player):
if world.mode[player] != 'inverted':
set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Flippers', player) or state.can_lift_rocks(player))
set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Flippers', player)) # can be fake flippered to
set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has('Flippers', player))
set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))
set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))
set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))
else:
""""""
if world.mode[player] == 'inverted':
set_rule(world.get_entrance('Zoras River', player), lambda state: state.has_Pearl(player) and (state.has('Flippers', player) or state.can_lift_rocks(player)))
set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # can be fake flippered to
set_rule(world.get_entrance('Lake Hylia Island Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # can be fake flippered to
@@ -708,6 +707,13 @@ def no_glitches_rules(world, player):
set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has('Flippers', player))
set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has('Flippers', player))
set_rule(world.get_entrance('East Dark World Pier', player), lambda state: state.has('Flippers', player))
else:
set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Flippers', player) or state.can_lift_rocks(player))
set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Flippers', player)) # can be fake flippered to
set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has('Flippers', player))
set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))
set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))
set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))
add_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: False) # no glitches does not require block override
@@ -715,14 +721,7 @@ def no_glitches_rules(world, player):
add_conditional_lamps(world, player)
def fake_flipper_rules(world, player):
if world.mode[player] != 'inverted':
set_rule(world.get_entrance('Zoras River', player), lambda state: True)
set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: True)
set_rule(world.get_entrance('Hobo Bridge', player), lambda state: True)
set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))
set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player))
set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has_Pearl(player))
else:
if world.mode[player] == 'inverted':
set_rule(world.get_entrance('Zoras River', player), lambda state: state.has_Pearl(player))
set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has_Pearl(player))
set_rule(world.get_entrance('Lake Hylia Island Pier', player), lambda state: state.has_Pearl(player))
@@ -733,6 +732,17 @@ def fake_flipper_rules(world, player):
set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: True)
set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: True)
set_rule(world.get_entrance('East Dark World Pier', player), lambda state: True)
#qirn jump
set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: True)
else:
set_rule(world.get_entrance('Zoras River', player), lambda state: True)
set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: True)
set_rule(world.get_entrance('Hobo Bridge', player), lambda state: True)
set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))
set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player))
set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has_Pearl(player))
#qirn jump
set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has_Pearl(player))
def forbid_bomb_jump_requirements(world, player):
@@ -1117,52 +1127,52 @@ def set_big_bomb_rules(world, player):
elif bombshop_entrance.name in Isolated_DW_entrances:
# 1. mirror then flute then basic routes
# -> M and Flute and BR
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and state.has('Flute', player) and basic_routes(state))
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and state.can_flute(player) and basic_routes(state))
elif bombshop_entrance.name in Isolated_LW_entrances:
# 1. flute then basic routes
# Prexisting mirror spot is not permitted, because mirror might have been needed to reach these isolated locations.
# -> Flute and BR
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Flute', player) and basic_routes(state))
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and basic_routes(state))
elif bombshop_entrance.name in West_LW_DM_entrances:
# 1. flute then basic routes or mirror
# Prexisting mirror spot is permitted, because flute can be used to reach west DM directly.
# -> Flute and (M or BR)
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Flute', player) and (state.has_Mirror(player) or basic_routes(state)))
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and (state.has_Mirror(player) or basic_routes(state)))
elif bombshop_entrance.name in East_LW_DM_entrances:
# 1. flute then basic routes or mirror and hookshot
# Prexisting mirror spot is permitted, because flute can be used to reach west DM directly and then east DM via Hookshot
# -> Flute and ((M and Hookshot) or BR)
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Flute', player) and ((state.has_Mirror(player) and state.has('Hookshot', player)) or basic_routes(state)))
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and ((state.has_Mirror(player) and state.has('Hookshot', player)) or basic_routes(state)))
elif bombshop_entrance.name == 'Fairy Ascension Cave (Bottom)':
# Same as East_LW_DM_entrances except navigation without BR requires Mitts
# -> Flute and ((M and Hookshot and Mitts) or BR)
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Flute', player) and ((state.has_Mirror(player) and state.has('Hookshot', player) and state.can_lift_heavy_rocks(player)) or basic_routes(state)))
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and ((state.has_Mirror(player) and state.has('Hookshot', player) and state.can_lift_heavy_rocks(player)) or basic_routes(state)))
elif bombshop_entrance.name in Castle_ledge_entrances:
# 1. mirror on pyramid to castle ledge, grab bomb, return through mirror spot: Needs mirror
# 2. flute then basic routes
# -> M or (Flute and BR)
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) or (state.has('Flute', player) and basic_routes(state)))
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) or (state.can_flute(player) and basic_routes(state)))
elif bombshop_entrance.name in Desert_mirrorable_ledge_entrances:
# Cases when you have mire access: Mirror to reach locations, return via mirror spot, move to center of desert, mirror anagin and:
# 1. Have mire access, Mirror to reach locations, return via mirror spot, move to center of desert, mirror again and then basic routes
# 2. flute then basic routes
# -> (Mire access and M) or Flute) and BR
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: ((state.can_reach('Dark Desert', 'Region', player) and state.has_Mirror(player)) or state.has('Flute', player)) and basic_routes(state))
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: ((state.can_reach('Dark Desert', 'Region', player) and state.has_Mirror(player)) or state.can_flute(player)) and basic_routes(state))
elif bombshop_entrance.name == 'Old Man Cave (West)':
# 1. Lift rock then basic_routes
# 2. flute then basic_routes
# -> (Flute or G) and BR
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flute', player) or state.can_lift_rocks(player)) and basic_routes(state))
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or state.can_lift_rocks(player)) and basic_routes(state))
elif bombshop_entrance.name == 'Graveyard Cave':
# 1. flute then basic routes
# 2. (has west dark world access) use existing mirror spot (required Pearl), mirror again off ledge
# -> (Flute or (M and P and West Dark World access) and BR
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flute', player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state))
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state))
elif bombshop_entrance.name in Mirror_from_SDW_entrances:
# 1. flute then basic routes
# 2. (has South dark world access) use existing mirror spot, mirror again off ledge
# -> (Flute or (M and South Dark World access) and BR
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flute', player) or (state.can_reach('South Dark World', 'Region', player) and state.has_Mirror(player))) and basic_routes(state))
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or (state.can_reach('South Dark World', 'Region', player) and state.has_Mirror(player))) and basic_routes(state))
elif bombshop_entrance.name == 'Dark World Potion Shop':
# 1. walk down by lifting rock: needs gloves and pearl`
# 2. walk down by hammering peg: needs hammer and pearl
@@ -1174,11 +1184,11 @@ def set_big_bomb_rules(world, player):
# (because otherwise mirror was used to reach the grave, so would cancel a pre-existing mirror spot)
# to account for insanity, must consider a way to escape without a cave for basic_routes
# -> (M and Mitts) or ((Mitts or Flute or (M and P and West Dark World access)) and BR)
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and state.has_Mirror(player)) or ((state.can_lift_heavy_rocks(player) or state.has('Flute', player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state)))
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and state.has_Mirror(player)) or ((state.can_lift_heavy_rocks(player) or state.can_flute(player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state)))
elif bombshop_entrance.name == 'Waterfall of Wishing':
# same as the Normal_LW_entrances case except in insanity it's possible you could be here without Flippers which
# means you need an escape route of either Flippers or Flute
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flippers', player) or state.has('Flute', player)) and (basic_routes(state) or state.has_Mirror(player)))
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flippers', player) or state.can_flute(player)) and (basic_routes(state) or state.has_Mirror(player)))
def set_inverted_big_bomb_rules(world, player):

View File

@@ -37,7 +37,6 @@ Uncle_texts = [
"Chasing tail.\nFly ladies.\nDo not follow.",
"I feel like\nI've done this\nbefore…",
"Magic Cape can\npass through\nthe barrier!",
"If this is a\nKanzeon seed,\nI'm quitting.",
"I am not your\nreal uncle.",
"You're going\nto have a very\nbad time.",
"Today you\nwill have\nbad luck.",
@@ -111,7 +110,6 @@ Triforce_texts = [
"\n We did it!",
"\n O M G",
" Hello. Will\n you be my\n friend?",
" Berserker\n was\n here!",
"The Wind Fish\nwill wake\nsoon. Hoot!",
"Meow meow meow\nMeow meow meow\n Oh my god!",
"Ahhhhhhhhh\nYa ya yaaaah\nYa ya yaaah",
@@ -175,6 +173,7 @@ Blind_texts = [
"Do I like\ndrills? Just\na bit.",
"I'd shell out\ngood rupees\nfor a conch.",
"Current\naffairs are\nshocking!",
"Agriculture\nis a growing\nfield."
]
Ganon1_texts = [
"Start your day\nsmiling with a\ndelicious\nwhole grain\nbreakfast\ncreated for\nyour\nincredible\ninsides.",
@@ -259,6 +258,16 @@ TavernMan_texts = [
]
junk_texts = [
"{C:GREEN}\nAgitha's good\nin Hyrule\nWarriors. >",
"{C:GREEN}\nConsult Fi if\nthe batteries\nare low. >",
"{C:GREEN}\nThere is no\n3rd quest in\nthis game. >",
"{C:GREEN}\nI am Error.\n \n >",
"{C:GREEN}\nThe Wind Fish\nknows all in\nhere. Hoot! >",
"{C:GREEN}\nThere are no\nwallets in\nthis game. >",
"{C:GREEN}\nCrossbow\nTraining is\na fun game. >",
"{C:GREEN}\nThe shrine\ncontains\nMagnesis. >",
"{C:GREEN}\nThe loftwing\nlet the duck\ntake over. >",
"{C:GREEN}\nStasis would\nbe very\noverpowered.>",
"{C:GREEN}\nIts a secret\nto everybody.\n >",
"{C:GREEN}\nDodongo\ndislikes\nsmoke. >",
"{C:GREEN}\n> Digdogger\nhates certain\nkind of sound.",
@@ -266,7 +275,7 @@ junk_texts = [
"{C:GREEN}\n>Secret power\nis said to be\nin the arrow.",
"{C:GREEN}\nAim at the\neyes of Gohma.\n >",
"{C:GREEN}\nGrumble,\ngrumble…\n >",
"{C:GREEN}\n10th enemy\nhas the bomb.\n >",
# "{C:GREEN}\n10th enemy\nhas the bomb.\n >", removed as people may assume it applies to this game
"{C:GREEN}\nGo to the\nnext room.\n >",
"{C:GREEN}\n>Thanks, @\nYoure the\nhero of Hyrule",
"{C:GREEN}\nTheres always\nmoney in the\nBanana Stand>",
@@ -290,9 +299,6 @@ junk_texts = [
"{C:GREEN}\nMist Form\nis in the\nCatacombs. >",
"{C:GREEN}\nMaybe you\ncould hire a\ndetective? >",
"{C:GREEN}\n> READ\nor the owl\nwill eat you.",
"{C:GREEN}\n> Bunnies\nare cute.",
"{C:GREEN}\nPugs are the\nsuperior dog\nbreed. >",
"{C:GREEN}\nThis is\nBerserker's\nMultiworld.>",
"{C:GREEN}\nOther randos\nexist too!\nTry some! >",
]
@@ -1229,7 +1235,8 @@ class GoldCreditMapper(CharTextMapper):
class GreenCreditMapper(CharTextMapper):
char_map = {' ': 0x9F,
'·': 0x52}
'·': 0x52,
'.': 0x52}
alpha_offset = -0x29
class RedCreditMapper(CharTextMapper):
@@ -1279,6 +1286,7 @@ class LargeCreditBottomMapper(CharTextMapper):
class TextTable(object):
SIZE = 0x7355
def __init__(self):
self._text = OrderedDict()
self.setDefaultText()
@@ -1286,6 +1294,9 @@ class TextTable(object):
def __getitem__(self, key):
return self._text[key]
def __contains__(self, key):
return key in self._text
def __setitem__(self, key, value):
if not key in self._text:
raise KeyError(key)
@@ -1504,7 +1515,7 @@ class TextTable(object):
text['tutorial_guard_4'] = CompressedTextMapper.convert("When you has a sword, press B to slash it.")
text['tutorial_guard_5'] = CompressedTextMapper.convert("このメッセージはニホンゴでそのまま") # on purpose
text['tutorial_guard_6'] = CompressedTextMapper.convert("Are we really still reading these?")
text['tutorial_guard_7'] = CompressedTextMapper.convert("Jeeze! There really are a lot of things.")
text['tutorial_guard_7'] = CompressedTextMapper.convert("Jeez! There really are a lot of things.")
text['priest_sanctuary_before_leave'] = CompressedTextMapper.convert("Go be a hero!")
text['sanctuary_enter'] = CompressedTextMapper.convert("YAY!\nYou saved Zelda!")
text['zelda_sanctuary_story'] = CompressedTextMapper.convert("Do you want to hear me say this again?\n{HARP}\n ≥ no\n yes\n{CHOICE}")
@@ -1664,12 +1675,12 @@ class TextTable(object):
text['tavern_old_man_know_tree_unactivated_flute'] = CompressedTextMapper.convert("You should play that flute for the weathervane, cause reasons.")
text['tavern_old_man_have_flute'] = CompressedTextMapper.convert("Life? Love? Happiness? The question you should really ask is: Was this generated by Stoops Alu or Stoops Jet?")
text['chicken_hut_lady'] = CompressedTextMapper.convert("This is\nChristos' hut.\n\nHe's out, searching for a bow.")
text['running_man'] = CompressedTextMapper.convert("Hi, Do you\nknow Veetorp?\n\nYou really\nshould. And\nall the other great guys who made this possible.\nGo thank them.\n\n\nIf you can catch them…")
text['running_man'] = CompressedTextMapper.convert("Catch me,\nIf you can!")
text['game_race_sign'] = CompressedTextMapper.convert("Why are you reading this sign? Run!!!")
text['sign_bumper_cave'] = CompressedTextMapper.convert("You need Cape, but not Hookshot")
text['sign_catfish'] = CompressedTextMapper.convert("toss rocks\ntoss items\ntoss cookies")
text['sign_north_village_of_outcasts'] = CompressedTextMapper.convert("↑ Skull Woods\n\n↓ Steve's Town")
text['sign_south_of_bumper_cave'] = CompressedTextMapper.convert("\nKarkats cave")
text['sign_south_of_bumper_cave'] = CompressedTextMapper.convert("\nDark Sanctuary")
text['sign_east_of_pyramid'] = CompressedTextMapper.convert("\n→ Dark Palace")
text['sign_east_of_bomb_shop'] = CompressedTextMapper.convert("\n← Bomb Shoppe")
text['sign_east_of_mire'] = CompressedTextMapper.convert("\n← Misery Mire\n no way in.\n no way out.")
@@ -1874,7 +1885,7 @@ class TextTable(object):
text['item_get_sanc_heart'] = CompressedTextMapper.convert("You got a whole ♥!\nGo you!")
text['fairy_fountain_refill'] = CompressedTextMapper.convert("Well done, lettuce have a cup of tea…")
text['death_mountain_bullied_no_pearl'] = CompressedTextMapper.convert("The following license applies to the base patch for the randomizer.\n\nCopyright (c) 2017 LLCoolDave\n\nCopyright (c) 2020 Berserker66\n\nCopyright (c) 2020 CaitSith2\n\nCopyright 2016, 2017 Equilateral IT\n\n Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.")
text['death_mountain_bullied_with_pearl'] = CompressedTextMapper.convert("The software is provided \"as is\", without warranty of any kind, express or implied, including but not limited to the warranties of\nmerchantability,\nfitness for a particular purpose and\nnoninfringement.\nIn no event shall the authors or copywight holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the software.")
text['death_mountain_bullied_with_pearl'] = CompressedTextMapper.convert("The software is provided \"as is\", without warranty of any kind, express or implied, including but not limited to the warranties of\nmerchantability,\nfitness for a particular purpose and\nnoninfringement.\nIn no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the software.")
text['death_mountain_bully_no_pearl'] = CompressedTextMapper.convert("Add garlic, ginger and apple and cook for 2 minutes. Add carrots, potatoes, garam masala and curry powder and stir well. Add tomato paste, stir well and slowly add red wine and bring to a boil. Add sugar, soy sauce and water, stir and bring to a boil again.")
text['death_mountain_bully_with_pearl'] = CompressedTextMapper.convert("I think I forgot how to smile…")
text['shop_darkworld_enter'] = CompressedTextMapper.convert("It's dangerous outside, buy my crap for safety.")
@@ -1897,7 +1908,7 @@ class TextTable(object):
text['ganon_fall_in'] = CompressedTextMapper.convert("You drove\naway my other\nself, Agahnim,\ntwo times…\nBut, I won't\ngive you the\nTriforce.\nI'll defeat\nyou!")
# 170
text['ganon_phase_3'] = CompressedTextMapper.convert("Can you beat\nmy darkness\ntechnique?")
text['lost_woods_thief'] = CompressedTextMapper.convert("Have you seen Andy?\n\nHe was out looking for our prized Ether medallion.\nI wonder when he will be back?")
text['lost_woods_thief'] = CompressedTextMapper.convert("Did you just vent?")
text['blinds_hut_dude'] = CompressedTextMapper.convert("I'm just some dude. This is Blind's hut.")
text['end_triforce'] = CompressedTextMapper.convert("{SPEED2}\n{MENU}\n{NOBORDER}\n G G")
text['toppi_fallen'] = CompressedTextMapper.convert("Ouch!\n\nYou Jerk!")
@@ -1906,7 +1917,7 @@ class TextTable(object):
text['thief_desert_rupee_cave'] = CompressedTextMapper.convert("So you, like, busted down my door, and are being a jerk by talking to me? Normally I would be angry and make you pay for it, but I bet you're just going to break all my pots and steal my 50 rupees.")
text['thief_ice_rupee_cave'] = CompressedTextMapper.convert("I'm a rupee pot farmer. One day I will take over the world with my skillz. Have you met my brother in the desert? He's way richer than I am.")
text['telepathic_tile_south_east_darkworld_cave'] = CompressedTextMapper.convert("~~ dev cave ~~\n no farming\n required")
text['cukeman'] = CompressedTextMapper.convert("Did you hear that Veetorp beat ajneb174 in a 1 on 1 race at AGDQ?")
text['cukeman'] = CompressedTextMapper.convert("Hey mon!")
text['cukeman_2'] = CompressedTextMapper.convert("You found Shabadoo, huh?\nNiiiiice.")
text['potion_shop_no_cash'] = CompressedTextMapper.convert("Yo! I'm not running a charity here.")
text['kakariko_powdered_chicken'] = CompressedTextMapper.convert("Smallhacker…\n\n\nWas hiding, you found me!\n\n\nOkay, you can leave now.")