LTTP: Update to options API (#4134)

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
This commit is contained in:
Nicholas Saylor
2025-04-18 17:41:38 -04:00
committed by GitHub
parent 1b51714f3b
commit 57a716b57a
22 changed files with 532 additions and 554 deletions

View File

@@ -102,7 +102,7 @@ def KholdstareDefeatRule(state, player: int) -> bool:
state.has('Fire Rod', player) or state.has('Fire Rod', player) or
( (
state.has('Bombos', player) and state.has('Bombos', player) and
(has_sword(state, player) or state.multiworld.swordless[player]) (has_sword(state, player) or state.multiworld.worlds[player].options.swordless)
) )
) and ) and
( (
@@ -111,7 +111,7 @@ def KholdstareDefeatRule(state, player: int) -> bool:
( (
state.has('Fire Rod', player) and state.has('Fire Rod', player) and
state.has('Bombos', player) and state.has('Bombos', player) and
state.multiworld.swordless[player] and state.multiworld.worlds[player].options.swordless and
can_extend_magic(state, player, 16) can_extend_magic(state, player, 16)
) )
) )
@@ -137,7 +137,7 @@ def AgahnimDefeatRule(state, player: int) -> bool:
def GanonDefeatRule(state, player: int) -> bool: def GanonDefeatRule(state, player: int) -> bool:
if state.multiworld.swordless[player]: if state.multiworld.worlds[player].options.swordless:
return state.has('Hammer', player) and \ return state.has('Hammer', player) and \
has_fire_source(state, player) and \ has_fire_source(state, player) and \
state.has('Silver Bow', player) and \ state.has('Silver Bow', player) and \
@@ -146,7 +146,7 @@ def GanonDefeatRule(state, player: int) -> bool:
can_hurt = has_beam_sword(state, player) can_hurt = has_beam_sword(state, player)
common = can_hurt and has_fire_source(state, player) common = can_hurt and has_fire_source(state, player)
# silverless ganon may be needed in anything higher than no glitches # silverless ganon may be needed in anything higher than no glitches
if state.multiworld.glitches_required[player] != 'no_glitches': if state.multiworld.worlds[player].options.glitches_required != 'no_glitches':
# need to light torch a sufficient amount of times # need to light torch a sufficient amount of times
return common and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or ( return common and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (
state.has('Silver Bow', player) and can_shoot_arrows(state, player)) or state.has('Silver Bow', player) and can_shoot_arrows(state, player)) or
@@ -248,7 +248,7 @@ for location in boss_location_table:
def place_boss(world: "ALTTPWorld", boss: str, location: str, level: Optional[str]) -> None: def place_boss(world: "ALTTPWorld", boss: str, location: str, level: Optional[str]) -> None:
player = world.player player = world.player
if location == 'Ganons Tower' and world.multiworld.mode[player] == 'inverted': if location == 'Ganons Tower' and world.options.mode == 'inverted':
location = 'Inverted Ganons Tower' location = 'Inverted Ganons Tower'
logging.debug('Placing boss %s at %s', boss, location + (' (' + level + ')' if level else '')) logging.debug('Placing boss %s at %s', boss, location + (' (' + level + ')' if level else ''))
world.dungeons[location].bosses[level] = BossFactory(boss, player) world.dungeons[location].bosses[level] = BossFactory(boss, player)
@@ -260,9 +260,8 @@ def format_boss_location(location_name: str, level: str) -> str:
def place_bosses(world: "ALTTPWorld") -> None: def place_bosses(world: "ALTTPWorld") -> None:
multiworld = world.multiworld multiworld = world.multiworld
player = world.player
# will either be an int or a lower case string with ';' between options # will either be an int or a lower case string with ';' between options
boss_shuffle: Union[str, int] = multiworld.boss_shuffle[player].value boss_shuffle: Union[str, int] = world.options.boss_shuffle.value
already_placed_bosses: List[str] = [] already_placed_bosses: List[str] = []
remaining_locations: List[Tuple[str, str]] = [] remaining_locations: List[Tuple[str, str]] = []
# handle plando # handle plando

View File

@@ -66,7 +66,7 @@ def create_dungeons(world: "ALTTPWorld"):
def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items): def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items):
dungeon = Dungeon(name, dungeon_regions, big_key, dungeon = Dungeon(name, dungeon_regions, big_key,
[] if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal else small_keys, [] if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal else small_keys,
dungeon_items, player) dungeon_items, player)
for item in dungeon.all_items: for item in dungeon.all_items:
item.dungeon = dungeon item.dungeon = dungeon
@@ -143,7 +143,7 @@ def create_dungeons(world: "ALTTPWorld"):
item_factory(['Small Key (Turtle Rock)'] * 6, world), item_factory(['Small Key (Turtle Rock)'] * 6, world),
item_factory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], world)) item_factory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], world))
if multiworld.mode[player] != 'inverted': if multiworld.worlds[player].options.mode != 'inverted':
AT = make_dungeon('Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None, AT = make_dungeon('Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None,
item_factory(['Small Key (Agahnims Tower)'] * 4, world), []) item_factory(['Small Key (Agahnims Tower)'] * 4, world), [])
GT = make_dungeon('Ganons Tower', 'Agahnim2', GT = make_dungeon('Ganons Tower', 'Agahnim2',

View File

@@ -23,17 +23,17 @@ def link_entrances(world, player):
connect_simple(world, exitname, regionname, player) connect_simple(world, exitname, regionname, player)
# if we do not shuffle, set default connections # if we do not shuffle, set default connections
if world.entrance_shuffle[player] == 'vanilla': if world.worlds[player].options.entrance_shuffle == 'vanilla':
for exitname, regionname in default_connections: for exitname, regionname in default_connections:
connect_simple(world, exitname, regionname, player) connect_simple(world, exitname, regionname, player)
for exitname, regionname in default_dungeon_connections: for exitname, regionname in default_dungeon_connections:
connect_simple(world, exitname, regionname, player) connect_simple(world, exitname, regionname, player)
elif world.entrance_shuffle[player] == 'dungeons_simple': elif world.worlds[player].options.entrance_shuffle == 'dungeons_simple':
for exitname, regionname in default_connections: for exitname, regionname in default_connections:
connect_simple(world, exitname, regionname, player) connect_simple(world, exitname, regionname, player)
simple_shuffle_dungeons(world, player) simple_shuffle_dungeons(world, player)
elif world.entrance_shuffle[player] == 'dungeons_full': elif world.worlds[player].options.entrance_shuffle == 'dungeons_full':
for exitname, regionname in default_connections: for exitname, regionname in default_connections:
connect_simple(world, exitname, regionname, player) connect_simple(world, exitname, regionname, player)
@@ -43,7 +43,7 @@ def link_entrances(world, player):
lw_entrances = list(LW_Dungeon_Entrances) lw_entrances = list(LW_Dungeon_Entrances)
dw_entrances = list(DW_Dungeon_Entrances) dw_entrances = list(DW_Dungeon_Entrances)
if world.mode[player] == 'standard': if world.worlds[player].options.mode == 'standard':
# must connect front of hyrule castle to do escape # must connect front of hyrule castle to do escape
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
else: else:
@@ -56,7 +56,7 @@ def link_entrances(world, player):
dw_entrances.append('Ganons Tower') dw_entrances.append('Ganons Tower')
dungeon_exits.append('Ganons Tower Exit') dungeon_exits.append('Ganons Tower Exit')
if world.mode[player] == 'standard': if world.worlds[player].options.mode == 'standard':
# rest of hyrule castle must be in light world, so it has to be the one connected to east exit of desert # rest of hyrule castle must be in light world, so it has to be the one connected to east exit of desert
hyrule_castle_exits = [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')] hyrule_castle_exits = [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')]
connect_mandatory_exits(world, lw_entrances, hyrule_castle_exits, list(LW_Dungeon_Entrances_Must_Exit), player) connect_mandatory_exits(world, lw_entrances, hyrule_castle_exits, list(LW_Dungeon_Entrances_Must_Exit), player)
@@ -65,9 +65,9 @@ def link_entrances(world, player):
connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player) connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player)
connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player) connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player)
connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player) connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player)
elif world.entrance_shuffle[player] == 'dungeons_crossed': elif world.worlds[player].options.entrance_shuffle == 'dungeons_crossed':
crossed_shuffle_dungeons(world, player) crossed_shuffle_dungeons(world, player)
elif world.entrance_shuffle[player] == 'simple': elif world.worlds[player].options.entrance_shuffle == 'simple':
simple_shuffle_dungeons(world, player) simple_shuffle_dungeons(world, player)
old_man_entrances = list(Old_Man_Entrances) old_man_entrances = list(Old_Man_Entrances)
@@ -138,7 +138,7 @@ def link_entrances(world, player):
# place remaining doors # place remaining doors
connect_doors(world, single_doors, door_targets, player) connect_doors(world, single_doors, door_targets, player)
elif world.entrance_shuffle[player] == 'restricted': elif world.worlds[player].options.entrance_shuffle == 'restricted':
simple_shuffle_dungeons(world, player) simple_shuffle_dungeons(world, player)
lw_entrances = list(LW_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances) lw_entrances = list(LW_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances)
@@ -210,7 +210,7 @@ def link_entrances(world, player):
# place remaining doors # place remaining doors
connect_doors(world, doors, door_targets, player) connect_doors(world, doors, door_targets, player)
elif world.entrance_shuffle[player] == 'full': elif world.worlds[player].options.entrance_shuffle == 'full':
skull_woods_shuffle(world, player) skull_woods_shuffle(world, player)
lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances) lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances)
@@ -227,7 +227,7 @@ def link_entrances(world, player):
# tavern back door cannot be shuffled yet # tavern back door cannot be shuffled yet
connect_doors(world, ['Tavern North'], ['Tavern'], player) connect_doors(world, ['Tavern North'], ['Tavern'], player)
if world.mode[player] == 'standard': if world.worlds[player].options.mode == 'standard':
# must connect front of hyrule castle to do escape # must connect front of hyrule castle to do escape
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
else: else:
@@ -264,7 +264,7 @@ def link_entrances(world, player):
pass pass
else: #if the cave wasn't placed we get here else: #if the cave wasn't placed we get here
connect_caves(world, lw_entrances, [], old_man_house, player) connect_caves(world, lw_entrances, [], old_man_house, player)
if world.mode[player] == 'standard': if world.worlds[player].options.mode == 'standard':
# rest of hyrule castle must be in light world # rest of hyrule castle must be in light world
connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
@@ -316,7 +316,7 @@ def link_entrances(world, player):
# place remaining doors # place remaining doors
connect_doors(world, doors, door_targets, player) connect_doors(world, doors, door_targets, player)
elif world.entrance_shuffle[player] == 'crossed': elif world.worlds[player].options.entrance_shuffle == 'crossed':
skull_woods_shuffle(world, player) skull_woods_shuffle(world, player)
entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances + DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors) entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances + DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors)
@@ -331,7 +331,7 @@ def link_entrances(world, player):
# tavern back door cannot be shuffled yet # tavern back door cannot be shuffled yet
connect_doors(world, ['Tavern North'], ['Tavern'], player) connect_doors(world, ['Tavern North'], ['Tavern'], player)
if world.mode[player] == 'standard': if world.worlds[player].options.mode == 'standard':
# must connect front of hyrule castle to do escape # must connect front of hyrule castle to do escape
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
else: else:
@@ -348,7 +348,7 @@ def link_entrances(world, player):
#place must-exit caves #place must-exit caves
connect_mandatory_exits(world, entrances, caves, must_exits, player) connect_mandatory_exits(world, entrances, caves, must_exits, player)
if world.mode[player] == 'standard': if world.worlds[player].options.mode == 'standard':
# rest of hyrule castle must be dealt with # rest of hyrule castle must be dealt with
connect_caves(world, entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) connect_caves(world, entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
@@ -394,7 +394,7 @@ def link_entrances(world, player):
# place remaining doors # place remaining doors
connect_doors(world, entrances, door_targets, player) connect_doors(world, entrances, door_targets, player)
elif world.entrance_shuffle[player] == 'insanity': elif world.worlds[player].options.entrance_shuffle == 'insanity':
# beware ye who enter here # beware ye who enter here
entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave'] entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave']
@@ -431,7 +431,7 @@ def link_entrances(world, player):
# tavern back door cannot be shuffled yet # tavern back door cannot be shuffled yet
connect_doors(world, ['Tavern North'], ['Tavern'], player) connect_doors(world, ['Tavern North'], ['Tavern'], player)
if world.mode[player] == 'standard': if world.worlds[player].options.mode == 'standard':
# cannot move uncle cave # cannot move uncle cave
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player) connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
@@ -464,7 +464,7 @@ def link_entrances(world, player):
connect_entrance(world, hole, hole_targets.pop(), player) connect_entrance(world, hole, hole_targets.pop(), player)
# hyrule castle handling # hyrule castle handling
if world.mode[player] == 'standard': if world.worlds[player].options.mode == 'standard':
# must connect front of hyrule castle to do escape # must connect front of hyrule castle to do escape
connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player) connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
@@ -544,12 +544,12 @@ def link_entrances(world, player):
else: else:
raise NotImplementedError( raise NotImplementedError(
f'{world.entrance_shuffle[player]} Shuffling not supported yet. Player {world.get_player_name(player)}') f'{world.worlds[player].options.entrance_shuffle} Shuffling not supported yet. Player {world.get_player_name(player)}')
if world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: if world.worlds[player].options.glitches_required in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']:
overworld_glitch_connections(world, player) overworld_glitch_connections(world, player)
# mandatory hybrid major glitches connections # mandatory hybrid major glitches connections
if world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']: if world.worlds[player].options.glitches_required in ['hybrid_major_glitches', 'no_logic']:
underworld_glitch_connections(world, player) underworld_glitch_connections(world, player)
# check for swamp palace fix # check for swamp palace fix
@@ -584,17 +584,17 @@ def link_inverted_entrances(world, player):
connect_simple(world, exitname, regionname, player) connect_simple(world, exitname, regionname, player)
# if we do not shuffle, set default connections # if we do not shuffle, set default connections
if world.entrance_shuffle[player] == 'vanilla': if world.worlds[player].options.entrance_shuffle == 'vanilla':
for exitname, regionname in inverted_default_connections: for exitname, regionname in inverted_default_connections:
connect_simple(world, exitname, regionname, player) connect_simple(world, exitname, regionname, player)
for exitname, regionname in inverted_default_dungeon_connections: for exitname, regionname in inverted_default_dungeon_connections:
connect_simple(world, exitname, regionname, player) connect_simple(world, exitname, regionname, player)
elif world.entrance_shuffle[player] == 'dungeons_simple': elif world.worlds[player].options.entrance_shuffle == 'dungeons_simple':
for exitname, regionname in inverted_default_connections: for exitname, regionname in inverted_default_connections:
connect_simple(world, exitname, regionname, player) connect_simple(world, exitname, regionname, player)
simple_shuffle_dungeons(world, player) simple_shuffle_dungeons(world, player)
elif world.entrance_shuffle[player] == 'dungeons_full': elif world.worlds[player].options.entrance_shuffle == 'dungeons_full':
for exitname, regionname in inverted_default_connections: for exitname, regionname in inverted_default_connections:
connect_simple(world, exitname, regionname, player) connect_simple(world, exitname, regionname, player)
@@ -649,9 +649,9 @@ def link_inverted_entrances(world, player):
connect_mandatory_exits(world, lw_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player) connect_mandatory_exits(world, lw_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player)
connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player) connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player)
elif world.entrance_shuffle[player] == 'dungeons_crossed': elif world.worlds[player].options.entrance_shuffle == 'dungeons_crossed':
inverted_crossed_shuffle_dungeons(world, player) inverted_crossed_shuffle_dungeons(world, player)
elif world.entrance_shuffle[player] == 'simple': elif world.worlds[player].options.entrance_shuffle == 'simple':
simple_shuffle_dungeons(world, player) simple_shuffle_dungeons(world, player)
old_man_entrances = list(Inverted_Old_Man_Entrances) old_man_entrances = list(Inverted_Old_Man_Entrances)
@@ -748,7 +748,7 @@ def link_inverted_entrances(world, player):
# place remaining doors # place remaining doors
connect_doors(world, single_doors, door_targets, player) connect_doors(world, single_doors, door_targets, player)
elif world.entrance_shuffle[player] == 'restricted': elif world.worlds[player].options.entrance_shuffle == 'restricted':
simple_shuffle_dungeons(world, player) simple_shuffle_dungeons(world, player)
lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Single_Cave_Doors) lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Single_Cave_Doors)
@@ -833,7 +833,7 @@ def link_inverted_entrances(world, player):
doors = lw_entrances + dw_entrances doors = lw_entrances + dw_entrances
# place remaining doors # place remaining doors
connect_doors(world, doors, door_targets, player) connect_doors(world, doors, door_targets, player)
elif world.entrance_shuffle[player] == 'full': elif world.worlds[player].options.entrance_shuffle == 'full':
skull_woods_shuffle(world, player) skull_woods_shuffle(world, player)
lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors) lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors)
@@ -984,7 +984,7 @@ def link_inverted_entrances(world, player):
# place remaining doors # place remaining doors
connect_doors(world, doors, door_targets, player) connect_doors(world, doors, door_targets, player)
elif world.entrance_shuffle[player] == 'crossed': elif world.worlds[player].options.entrance_shuffle == 'crossed':
skull_woods_shuffle(world, player) skull_woods_shuffle(world, player)
entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors + Inverted_Old_Man_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_DW_Single_Cave_Doors) entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors + Inverted_Old_Man_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_DW_Single_Cave_Doors)
@@ -1095,7 +1095,7 @@ def link_inverted_entrances(world, player):
# place remaining doors # place remaining doors
connect_doors(world, entrances, door_targets, player) connect_doors(world, entrances, door_targets, player)
elif world.entrance_shuffle[player] == 'insanity': elif world.worlds[player].options.entrance_shuffle == 'insanity':
# beware ye who enter here # beware ye who enter here
entrances = Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Entrance (South)'] entrances = Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Entrance (South)']
@@ -1254,10 +1254,10 @@ def link_inverted_entrances(world, player):
else: else:
raise NotImplementedError('Shuffling not supported yet') raise NotImplementedError('Shuffling not supported yet')
if world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: if world.worlds[player].options.glitches_required in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']:
overworld_glitch_connections(world, player) overworld_glitch_connections(world, player)
# mandatory hybrid major glitches connections # mandatory hybrid major glitches connections
if world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']: if world.worlds[player].options.glitches_required in ['hybrid_major_glitches', 'no_logic']:
underworld_glitch_connections(world, player) underworld_glitch_connections(world, player)
# patch swamp drain # patch swamp drain
@@ -1349,7 +1349,7 @@ def scramble_holes(world, player):
else: else:
hole_targets.append(('Pyramid Exit', 'Pyramid')) hole_targets.append(('Pyramid Exit', 'Pyramid'))
if world.mode[player] == 'standard': if world.worlds[player].options.mode == 'standard':
# cannot move uncle cave # cannot move uncle cave
connect_two_way(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player) connect_two_way(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player)
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
@@ -1358,14 +1358,14 @@ def scramble_holes(world, player):
hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance')) hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance'))
# do not shuffle sanctuary into pyramid hole unless shuffle is crossed # do not shuffle sanctuary into pyramid hole unless shuffle is crossed
if world.entrance_shuffle[player] == 'crossed': if world.worlds[player].options.entrance_shuffle == 'crossed':
hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
if world.shuffle_ganon: if world.shuffle_ganon:
world.random.shuffle(hole_targets) world.random.shuffle(hole_targets)
exit, target = hole_targets.pop() exit, target = hole_targets.pop()
connect_two_way(world, 'Pyramid Entrance', exit, player) connect_two_way(world, 'Pyramid Entrance', exit, player)
connect_entrance(world, 'Pyramid Hole', target, player) connect_entrance(world, 'Pyramid Hole', target, player)
if world.entrance_shuffle[player] != 'crossed': if world.worlds[player].options.entrance_shuffle != 'crossed':
hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
world.random.shuffle(hole_targets) world.random.shuffle(hole_targets)
@@ -1400,14 +1400,14 @@ def scramble_inverted_holes(world, player):
hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance')) hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance'))
# do not shuffle sanctuary into pyramid hole unless shuffle is crossed # do not shuffle sanctuary into pyramid hole unless shuffle is crossed
if world.entrance_shuffle[player] == 'crossed': if world.worlds[player].options.entrance_shuffle == 'crossed':
hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
if world.shuffle_ganon: if world.shuffle_ganon:
world.random.shuffle(hole_targets) world.random.shuffle(hole_targets)
exit, target = hole_targets.pop() exit, target = hole_targets.pop()
connect_two_way(world, 'Inverted Pyramid Entrance', exit, player) connect_two_way(world, 'Inverted Pyramid Entrance', exit, player)
connect_entrance(world, 'Inverted Pyramid Hole', target, player) connect_entrance(world, 'Inverted Pyramid Hole', target, player)
if world.entrance_shuffle[player] != 'crossed': if world.worlds[player].options.entrance_shuffle != 'crossed':
hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
world.random.shuffle(hole_targets) world.random.shuffle(hole_targets)
@@ -1430,15 +1430,15 @@ def connect_random(world, exitlist, targetlist, player, two_way=False):
def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): def connect_mandatory_exits(world, entrances, caves, must_be_exits, player):
# Keeps track of entrances that cannot be used to access each exit / cave # Keeps track of entrances that cannot be used to access each exit / cave
if world.mode[player] == 'inverted': if world.worlds[player].options.mode == 'inverted':
invalid_connections = Inverted_Must_Exit_Invalid_Connections.copy() invalid_connections = Inverted_Must_Exit_Invalid_Connections.copy()
else: else:
invalid_connections = Must_Exit_Invalid_Connections.copy() invalid_connections = Must_Exit_Invalid_Connections.copy()
invalid_cave_connections = defaultdict(set) invalid_cave_connections = defaultdict(set)
if world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: if world.worlds[player].options.glitches_required in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']:
from . import OverworldGlitchRules from . import OverworldGlitchRules
for entrance in OverworldGlitchRules.get_non_mandatory_exits(world.mode[player] == 'inverted'): for entrance in OverworldGlitchRules.get_non_mandatory_exits(world.worlds[player].options.mode == 'inverted'):
invalid_connections[entrance] = set() invalid_connections[entrance] = set()
if entrance in must_be_exits: if entrance in must_be_exits:
must_be_exits.remove(entrance) must_be_exits.remove(entrance)
@@ -1449,7 +1449,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player):
world.random.shuffle(caves) world.random.shuffle(caves)
# Handle inverted Aga Tower - if it depends on connections, then so does Hyrule Castle Ledge # Handle inverted Aga Tower - if it depends on connections, then so does Hyrule Castle Ledge
if world.mode[player] == 'inverted': if world.worlds[player].options.mode == 'inverted':
for entrance in invalid_connections: for entrance in invalid_connections:
if world.get_entrance(entrance, player).connected_region == world.get_region('Inverted Agahnims Tower', if world.get_entrance(entrance, player).connected_region == world.get_region('Inverted Agahnims Tower',
player): player):
@@ -1490,7 +1490,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player):
entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit]) entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit])
cave_entrances.append(entrance) cave_entrances.append(entrance)
entrances.remove(entrance) entrances.remove(entrance)
connect_two_way(world,entrance,cave_exit, player) connect_two_way(world, entrance, cave_exit, player)
if entrance not in invalid_connections: if entrance not in invalid_connections:
invalid_connections[exit] = set() invalid_connections[exit] = set()
if all(entrance in invalid_connections for entrance in cave_entrances): if all(entrance in invalid_connections for entrance in cave_entrances):
@@ -1564,7 +1564,7 @@ def simple_shuffle_dungeons(world, player):
dungeon_entrances = ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section', 'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace'] dungeon_entrances = ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section', 'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace']
dungeon_exits = ['Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit', 'Skull Woods Final Section Exit', 'Palace of Darkness Exit', 'Ice Palace Exit', 'Misery Mire Exit', 'Swamp Palace Exit'] dungeon_exits = ['Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit', 'Skull Woods Final Section Exit', 'Palace of Darkness Exit', 'Ice Palace Exit', 'Misery Mire Exit', 'Swamp Palace Exit']
if world.mode[player] != 'inverted': if world.worlds[player].options.mode != 'inverted':
if not world.shuffle_ganon: if not world.shuffle_ganon:
connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player)
else: else:
@@ -1579,13 +1579,13 @@ def simple_shuffle_dungeons(world, player):
# mix up 4 door dungeons # mix up 4 door dungeons
multi_dungeons = ['Desert', 'Turtle Rock'] multi_dungeons = ['Desert', 'Turtle Rock']
if world.mode[player] == 'open' or (world.mode[player] == 'inverted' and world.shuffle_ganon): if world.worlds[player].options.mode == 'open' or (world.worlds[player].options.mode == 'inverted' and world.shuffle_ganon):
multi_dungeons.append('Hyrule Castle') multi_dungeons.append('Hyrule Castle')
world.random.shuffle(multi_dungeons) world.random.shuffle(multi_dungeons)
dp_target = multi_dungeons[0] dp_target = multi_dungeons[0]
tr_target = multi_dungeons[1] tr_target = multi_dungeons[1]
if world.mode[player] not in ['open', 'inverted'] or (world.mode[player] == 'inverted' and world.shuffle_ganon is False): if world.worlds[player].options.mode not in ['open', 'inverted'] or (world.worlds[player].options.mode == 'inverted' and world.shuffle_ganon is False):
# place hyrule castle as intended # place hyrule castle as intended
hc_target = 'Hyrule Castle' hc_target = 'Hyrule Castle'
else: else:
@@ -1593,7 +1593,7 @@ def simple_shuffle_dungeons(world, player):
# ToDo improve this? # ToDo improve this?
if world.mode[player] != 'inverted': if world.worlds[player].options.mode != 'inverted':
if hc_target == 'Hyrule Castle': if hc_target == 'Hyrule Castle':
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Exit (East)', player) connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Exit (East)', player)
@@ -1708,7 +1708,7 @@ def crossed_shuffle_dungeons(world, player: int):
dungeon_entrances.append('Ganons Tower') dungeon_entrances.append('Ganons Tower')
dungeon_exits.append('Ganons Tower Exit') dungeon_exits.append('Ganons Tower Exit')
if world.mode[player] == 'standard': if world.worlds[player].options.mode == 'standard':
# must connect front of hyrule castle to do escape # must connect front of hyrule castle to do escape
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
else: else:
@@ -1718,7 +1718,7 @@ def crossed_shuffle_dungeons(world, player: int):
connect_mandatory_exits(world, dungeon_entrances, dungeon_exits, connect_mandatory_exits(world, dungeon_entrances, dungeon_exits,
LW_Dungeon_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit, player) LW_Dungeon_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit, player)
if world.mode[player] == 'standard': if world.worlds[player].options.mode == 'standard':
connect_caves(world, dungeon_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) connect_caves(world, dungeon_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
connect_caves(world, dungeon_entrances, [], dungeon_exits, player) connect_caves(world, dungeon_entrances, [], dungeon_exits, player)
@@ -1823,14 +1823,14 @@ lookup = {
def plando_connect(world, player: int): def plando_connect(world, player: int):
if world.plando_connections[player]: if world.worlds[player].options.plando_connections:
for connection in world.plando_connections[player]: for connection in world.worlds[player].options.plando_connections:
func = lookup[connection.direction] func = lookup[connection.direction]
try: try:
func(world, connection.entrance, connection.exit, player) func(world, connection.entrance, connection.exit, player)
except Exception as e: except Exception as e:
raise Exception(f"Could not connect using {connection}") from e raise Exception(f"Could not connect using {connection}") from e
if world.mode[player] != 'inverted': if world.worlds[player].options.mode != 'inverted':
mark_light_world_regions(world, player) mark_light_world_regions(world, player)
else: else:
mark_dark_world_regions(world, player) mark_dark_world_regions(world, player)

View File

@@ -226,25 +226,25 @@ def generate_itempool(world):
player = world.player player = world.player
multiworld = world.multiworld multiworld = world.multiworld
if multiworld.item_pool[player].current_key not in difficulties: if world.options.item_pool.current_key not in difficulties:
raise NotImplementedError(f"Diffulty {multiworld.item_pool[player]}") raise NotImplementedError(f"Diffulty {world.options.item_pool}")
if multiworld.goal[player] not in ('ganon', 'pedestal', 'bosses', 'triforce_hunt', 'local_triforce_hunt', if world.options.goal not in ('ganon', 'pedestal', 'bosses', 'triforce_hunt', 'local_triforce_hunt',
'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'crystals',
'ganon_pedestal'): 'ganon_pedestal'):
raise NotImplementedError(f"Goal {multiworld.goal[player]} for player {player}") raise NotImplementedError(f"Goal {world.options.goal} for player {player}")
if multiworld.mode[player] not in ('open', 'standard', 'inverted'): if world.options.mode not in ('open', 'standard', 'inverted'):
raise NotImplementedError(f"Mode {multiworld.mode[player]} for player {player}") raise NotImplementedError(f"Mode {world.options.mode} for player {player}")
if multiworld.timer[player] not in (False, 'display', 'timed', 'timed_ohko', 'ohko', 'timed_countdown'): if world.options.timer not in (False, 'display', 'timed', 'timed_ohko', 'ohko', 'timed_countdown'):
raise NotImplementedError(f"Timer {multiworld.timer[player]} for player {player}") raise NotImplementedError(f"Timer {world.options.timer} for player {player}")
if multiworld.timer[player] in ['ohko', 'timed_ohko']: if world.options.timer in ['ohko', 'timed_ohko']:
world.can_take_damage = False world.can_take_damage = False
if multiworld.goal[player] in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']: if world.options.goal in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']:
multiworld.push_item(multiworld.get_location('Ganon', player), item_factory('Nothing', world), False) multiworld.push_item(multiworld.get_location('Ganon', player), item_factory('Nothing', world), False)
else: else:
multiworld.push_item(multiworld.get_location('Ganon', player), item_factory('Triforce', world), False) multiworld.push_item(multiworld.get_location('Ganon', player), item_factory('Triforce', world), False)
if multiworld.goal[player] in ['triforce_hunt', 'local_triforce_hunt']: if world.options.goal in ['triforce_hunt', 'local_triforce_hunt']:
region = multiworld.get_region('Light World', player) region = multiworld.get_region('Light World', player)
loc = ALttPLocation(player, "Murahdahla", parent=region) loc = ALttPLocation(player, "Murahdahla", parent=region)
@@ -288,7 +288,7 @@ def generate_itempool(world):
for item in precollected_items: for item in precollected_items:
multiworld.push_precollected(item_factory(item, world)) multiworld.push_precollected(item_factory(item, world))
if multiworld.mode[player] == 'standard' and not has_melee_weapon(multiworld.state, player): if world.options.mode == 'standard' and not has_melee_weapon(multiworld.state, player):
if "Link's Uncle" not in placed_items: if "Link's Uncle" not in placed_items:
found_sword = False found_sword = False
found_bow = False found_bow = False
@@ -304,10 +304,10 @@ def generate_itempool(world):
elif item in ['Hammer', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']: elif item in ['Hammer', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']:
if item not in possible_weapons: if item not in possible_weapons:
possible_weapons.append(item) possible_weapons.append(item)
elif (item == 'Bombs (10)' and (not multiworld.bombless_start[player]) and item not in elif (item == 'Bombs (10)' and (not world.options.bombless_start) and item not in
possible_weapons): possible_weapons):
possible_weapons.append(item) possible_weapons.append(item)
elif (item in ['Bomb Upgrade (+10)', 'Bomb Upgrade (50)'] and multiworld.bombless_start[player] and item elif (item in ['Bomb Upgrade (+10)', 'Bomb Upgrade (50)'] and world.options.bombless_start and item
not in possible_weapons): not in possible_weapons):
possible_weapons.append(item) possible_weapons.append(item)
@@ -315,21 +315,21 @@ def generate_itempool(world):
placed_items["Link's Uncle"] = starting_weapon placed_items["Link's Uncle"] = starting_weapon
pool.remove(starting_weapon) pool.remove(starting_weapon)
if (placed_items["Link's Uncle"] in ['Bow', 'Progressive Bow', 'Bombs (10)', 'Bomb Upgrade (+10)', if (placed_items["Link's Uncle"] in ['Bow', 'Progressive Bow', 'Bombs (10)', 'Bomb Upgrade (+10)',
'Bomb Upgrade (50)', 'Cane of Somaria', 'Cane of Byrna'] and multiworld.enemy_health[player] not in ['default', 'easy']): 'Bomb Upgrade (50)', 'Cane of Somaria', 'Cane of Byrna'] and world.options.enemy_health not in ['default', 'easy']):
if multiworld.bombless_start[player] and "Bomb Upgrade" not in placed_items["Link's Uncle"]: if world.options.bombless_start and "Bomb Upgrade" not in placed_items["Link's Uncle"]:
if 'Bow' in placed_items["Link's Uncle"]: if 'Bow' in placed_items["Link's Uncle"]:
multiworld.worlds[player].escape_assist.append('arrows') world.escape_assist.append('arrows')
elif 'Cane' in placed_items["Link's Uncle"]: elif 'Cane' in placed_items["Link's Uncle"]:
multiworld.worlds[player].escape_assist.append('magic') world.escape_assist.append('magic')
else: else:
multiworld.worlds[player].escape_assist.append('bombs') world.escape_assist.append('bombs')
for (location, item) in placed_items.items(): for (location, item) in placed_items.items():
multiworld.get_location(location, player).place_locked_item(item_factory(item, world)) multiworld.get_location(location, player).place_locked_item(item_factory(item, world))
items = item_factory(pool, world) items = item_factory(pool, world)
# convert one Progressive Bow into Progressive Bow (Alt), in ID only, for ganon silvers hint text # convert one Progressive Bow into Progressive Bow (Alt), in ID only, for ganon silvers hint text
if multiworld.worlds[player].has_progressive_bows: if world.has_progressive_bows:
for item in items: for item in items:
if item.code == 0x64: # Progressive Bow if item.code == 0x64: # Progressive Bow
item.code = 0x65 # Progressive Bow (Alt) item.code = 0x65 # Progressive Bow (Alt)
@@ -338,21 +338,21 @@ def generate_itempool(world):
if clock_mode: if clock_mode:
world.clock_mode = clock_mode world.clock_mode = clock_mode
multiworld.worlds[player].treasure_hunt_required = treasure_hunt_required % 999 world.treasure_hunt_required = treasure_hunt_required % 999
multiworld.worlds[player].treasure_hunt_total = treasure_hunt_total world.treasure_hunt_total = treasure_hunt_total
dungeon_items = [item for item in get_dungeon_item_pool_player(world) dungeon_items = [item for item in get_dungeon_item_pool_player(world)
if item.name not in multiworld.worlds[player].dungeon_local_item_names] if item.name not in world.dungeon_local_item_names]
for key_loc in key_drop_data: for key_loc in key_drop_data:
key_data = key_drop_data[key_loc] key_data = key_drop_data[key_loc]
drop_item = item_factory(key_data[3], world) drop_item = item_factory(key_data[3], world)
if not multiworld.key_drop_shuffle[player]: if not world.options.key_drop_shuffle:
if drop_item in dungeon_items: if drop_item in dungeon_items:
dungeon_items.remove(drop_item) dungeon_items.remove(drop_item)
else: else:
dungeon = drop_item.name.split("(")[1].split(")")[0] dungeon = drop_item.name.split("(")[1].split(")")[0]
if multiworld.mode[player] == 'inverted': if world.options.mode == 'inverted':
if dungeon == "Agahnims Tower": if dungeon == "Agahnims Tower":
dungeon = "Inverted Agahnims Tower" dungeon = "Inverted Agahnims Tower"
if dungeon == "Ganons Tower": if dungeon == "Ganons Tower":
@@ -365,7 +365,7 @@ def generate_itempool(world):
loc = multiworld.get_location(key_loc, player) loc = multiworld.get_location(key_loc, player)
loc.place_locked_item(drop_item) loc.place_locked_item(drop_item)
loc.address = None loc.address = None
elif "Small" in key_data[3] and multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: elif "Small" in key_data[3] and world.options.small_key_shuffle == small_key_shuffle.option_universal:
# key drop shuffle and universal keys are on. Add universal keys in place of key drop keys. # key drop shuffle and universal keys are on. Add universal keys in place of key drop keys.
multiworld.itempool.append(item_factory(GetBeemizerItem(multiworld, player, 'Small Key (Universal)'), world)) multiworld.itempool.append(item_factory(GetBeemizerItem(multiworld, player, 'Small Key (Universal)'), world))
dungeon_item_replacements = sum(difficulties[world.options.item_pool.current_key].extras, []) * 2 dungeon_item_replacements = sum(difficulties[world.options.item_pool.current_key].extras, []) * 2
@@ -373,10 +373,10 @@ def generate_itempool(world):
for x in range(len(dungeon_items)-1, -1, -1): for x in range(len(dungeon_items)-1, -1, -1):
item = dungeon_items[x] item = dungeon_items[x]
if ((multiworld.small_key_shuffle[player] == small_key_shuffle.option_start_with and item.type == 'SmallKey') if ((world.options.small_key_shuffle == small_key_shuffle.option_start_with and item.type == 'SmallKey')
or (multiworld.big_key_shuffle[player] == big_key_shuffle.option_start_with and item.type == 'BigKey') or (world.options.big_key_shuffle == big_key_shuffle.option_start_with and item.type == 'BigKey')
or (multiworld.compass_shuffle[player] == compass_shuffle.option_start_with and item.type == 'Compass') or (world.options.compass_shuffle == compass_shuffle.option_start_with and item.type == 'Compass')
or (multiworld.map_shuffle[player] == map_shuffle.option_start_with and item.type == 'Map')): or (world.options.map_shuffle == map_shuffle.option_start_with and item.type == 'Map')):
dungeon_items.pop(x) dungeon_items.pop(x)
multiworld.push_precollected(item) multiworld.push_precollected(item)
multiworld.itempool.append(item_factory(dungeon_item_replacements.pop(), world)) multiworld.itempool.append(item_factory(dungeon_item_replacements.pop(), world))
@@ -384,7 +384,7 @@ def generate_itempool(world):
set_up_shops(multiworld, player) set_up_shops(multiworld, player)
if multiworld.retro_bow[player]: if world.options.retro_bow:
shop_items = 0 shop_items = 0
shop_locations = [location for shop_locations in (shop.region.locations for shop in multiworld.shops if shop_locations = [location for shop_locations in (shop.region.locations for shop in multiworld.shops if
shop.type == ShopType.Shop and shop.region.player == player) for location in shop_locations if shop.type == ShopType.Shop and shop.region.player == player) for location in shop_locations if
@@ -395,12 +395,12 @@ def generate_itempool(world):
else: else:
shop_items += 1 shop_items += 1
else: else:
shop_items = min(multiworld.shop_item_slots[player], 30 if multiworld.include_witch_hut[player] else 27) shop_items = min(world.options.shop_item_slots, 30 if world.options.include_witch_hut else 27)
if multiworld.shuffle_capacity_upgrades[player]: if world.options.shuffle_capacity_upgrades:
shop_items += 2 shop_items += 2
chance_100 = int(multiworld.retro_bow[player]) * 0.25 + int( chance_100 = int(world.options.retro_bow) * 0.25 + int(
multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal) * 0.5 world.options.small_key_shuffle == small_key_shuffle.option_universal) * 0.5
for _ in range(shop_items): for _ in range(shop_items):
if multiworld.random.random() < chance_100: if multiworld.random.random() < chance_100:
items.append(item_factory(GetBeemizerItem(multiworld, player, "Rupees (100)"), world)) items.append(item_factory(GetBeemizerItem(multiworld, player, "Rupees (100)"), world))
@@ -410,19 +410,19 @@ def generate_itempool(world):
multiworld.random.shuffle(items) multiworld.random.shuffle(items)
pool_count = len(items) pool_count = len(items)
new_items = ["Triforce Piece" for _ in range(additional_triforce_pieces)] new_items = ["Triforce Piece" for _ in range(additional_triforce_pieces)]
if multiworld.shuffle_capacity_upgrades[player] or multiworld.bombless_start[player]: if world.options.shuffle_capacity_upgrades or world.options.bombless_start:
progressive = multiworld.progressive[player] progressive = world.options.progressive
progressive = multiworld.random.choice([True, False]) if progressive == 'grouped_random' else progressive == 'on' progressive = multiworld.random.choice([True, False]) if progressive == 'grouped_random' else progressive == 'on'
if multiworld.shuffle_capacity_upgrades[player] == "on_combined": if world.options.shuffle_capacity_upgrades == "on_combined":
new_items.append("Bomb Upgrade (50)") new_items.append("Bomb Upgrade (50)")
elif multiworld.shuffle_capacity_upgrades[player] == "on": elif world.options.shuffle_capacity_upgrades == "on":
new_items += ["Bomb Upgrade (+5)"] * 6 new_items += ["Bomb Upgrade (+5)"] * 6
new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)") new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")
if multiworld.shuffle_capacity_upgrades[player] != "on_combined" and multiworld.bombless_start[player]: if world.options.shuffle_capacity_upgrades != "on_combined" and world.options.bombless_start:
new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)") new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")
if multiworld.shuffle_capacity_upgrades[player] and not multiworld.retro_bow[player]: if world.options.shuffle_capacity_upgrades and not world.options.retro_bow:
if multiworld.shuffle_capacity_upgrades[player] == "on_combined": if world.options.shuffle_capacity_upgrades == "on_combined":
new_items += ["Arrow Upgrade (70)"] new_items += ["Arrow Upgrade (70)"]
else: else:
new_items += ["Arrow Upgrade (+5)"] * 6 new_items += ["Arrow Upgrade (+5)"] * 6
@@ -481,7 +481,7 @@ def generate_itempool(world):
if len(items) < pool_count: if len(items) < pool_count:
items += removed_filler[len(items) - pool_count:] items += removed_filler[len(items) - pool_count:]
if multiworld.randomize_cost_types[player]: if world.options.randomize_cost_types:
# Heart and Arrow costs require all Heart Container/Pieces and Arrow Upgrades to be advancement items for logic # Heart and Arrow costs require all Heart Container/Pieces and Arrow Upgrades to be advancement items for logic
for item in items: for item in items:
if item.name in ("Boss Heart Container", "Sanctuary Heart Container", "Piece of Heart"): if item.name in ("Boss Heart Container", "Sanctuary Heart Container", "Piece of Heart"):
@@ -490,21 +490,21 @@ def generate_itempool(world):
# Otherwise, logic has some branches where having 4 hearts is one possible requirement (of several alternatives) # Otherwise, logic has some branches where having 4 hearts is one possible requirement (of several alternatives)
# rather than making all hearts/heart pieces progression items (which slows down generation considerably) # rather than making all hearts/heart pieces progression items (which slows down generation considerably)
# We mark one random heart container as an advancement item (or 4 heart pieces in expert mode) # We mark one random heart container as an advancement item (or 4 heart pieces in expert mode)
if multiworld.item_pool[player] in ['easy', 'normal', 'hard'] and not (multiworld.custom and multiworld.customitemarray[30] == 0): if world.options.item_pool in ['easy', 'normal', 'hard'] and not (multiworld.custom and multiworld.customitemarray[30] == 0):
next(item for item in items if item.name == 'Boss Heart Container').classification = ItemClassification.progression next(item for item in items if item.name == 'Boss Heart Container').classification = ItemClassification.progression
elif multiworld.item_pool[player] in ['expert'] and not (multiworld.custom and multiworld.customitemarray[29] < 4): elif world.options.item_pool in ['expert'] and not (multiworld.custom and multiworld.customitemarray[29] < 4):
adv_heart_pieces = (item for item in items if item.name == 'Piece of Heart') adv_heart_pieces = (item for item in items if item.name == 'Piece of Heart')
for i in range(4): for i in range(4):
next(adv_heart_pieces).classification = ItemClassification.progression next(adv_heart_pieces).classification = ItemClassification.progression
world.required_medallions = (multiworld.misery_mire_medallion[player].current_key.title(), world.required_medallions = (world.options.misery_mire_medallion.current_key.title(),
multiworld.turtle_rock_medallion[player].current_key.title()) world.options.turtle_rock_medallion.current_key.title())
place_bosses(world) place_bosses(world)
multiworld.itempool += items multiworld.itempool += items
if multiworld.retro_caves[player]: if world.options.retro_caves:
set_up_take_anys(multiworld, world, player) # depends on world.itempool to be set set_up_take_anys(multiworld, world, player) # depends on world.itempool to be set
@@ -527,7 +527,7 @@ take_any_locations.sort()
def set_up_take_anys(multiworld, world, player): def set_up_take_anys(multiworld, world, player):
# these are references, do not modify these lists in-place # these are references, do not modify these lists in-place
if multiworld.mode[player] == 'inverted': if world.options.mode == 'inverted':
take_any_locs = take_any_locations_inverted take_any_locs = take_any_locations_inverted
else: else:
take_any_locs = take_any_locations take_any_locs = take_any_locations
@@ -578,14 +578,14 @@ def set_up_take_anys(multiworld, world, player):
def get_pool_core(world, player: int): def get_pool_core(world, player: int):
shuffle = world.entrance_shuffle[player].current_key shuffle = world.worlds[player].options.entrance_shuffle.current_key
difficulty = world.item_pool[player].current_key difficulty = world.worlds[player].options.item_pool.current_key
timer = world.timer[player].current_key timer = world.worlds[player].options.timer.current_key
goal = world.goal[player].current_key goal = world.worlds[player].options.goal.current_key
mode = world.mode[player].current_key mode = world.worlds[player].options.mode.current_key
swordless = world.swordless[player] swordless = world.worlds[player].options.swordless
retro_bow = world.retro_bow[player] retro_bow = world.worlds[player].options.retro_bow
logic = world.glitches_required[player] logic = world.worlds[player].options.glitches_required
pool = [] pool = []
placed_items = {} placed_items = {}
@@ -602,11 +602,11 @@ def get_pool_core(world, player: int):
placed_items[loc] = item placed_items[loc] = item
# provide boots to major glitch dependent seeds # provide boots to major glitch dependent seeds
if logic.current_key in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.glitch_boots[player]: if logic.current_key in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.worlds[player].options.glitch_boots:
precollected_items.append('Pegasus Boots') precollected_items.append('Pegasus Boots')
pool.remove('Pegasus Boots') pool.remove('Pegasus Boots')
pool.append('Rupees (20)') pool.append('Rupees (20)')
want_progressives = world.progressive[player].want_progressives want_progressives = world.worlds[player].options.progressive.want_progressives
if want_progressives(world.random): if want_progressives(world.random):
pool.extend(diff.progressiveglove) pool.extend(diff.progressiveglove)
@@ -680,22 +680,22 @@ def get_pool_core(world, player: int):
additional_pieces_to_place = 0 additional_pieces_to_place = 0
if 'triforce_hunt' in goal: if 'triforce_hunt' in goal:
if world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_extra: if world.worlds[player].options.triforce_pieces_mode.value == TriforcePiecesMode.option_extra:
treasure_hunt_total = (world.triforce_pieces_required[player].value treasure_hunt_total = (world.worlds[player].options.triforce_pieces_required.value
+ world.triforce_pieces_extra[player].value) + world.worlds[player].options.triforce_pieces_extra.value)
elif world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_percentage: elif world.worlds[player].options.triforce_pieces_mode.value == TriforcePiecesMode.option_percentage:
percentage = float(world.triforce_pieces_percentage[player].value) / 100 percentage = float(world.worlds[player].options.triforce_pieces_percentage.value) / 100
treasure_hunt_total = int(round(world.triforce_pieces_required[player].value * percentage, 0)) treasure_hunt_total = int(round(world.worlds[player].options.triforce_pieces_required.value * percentage, 0))
else: # available else: # available
treasure_hunt_total = world.triforce_pieces_available[player].value treasure_hunt_total = world.worlds[player].options.triforce_pieces_available.value
triforce_pieces = min(90, max(treasure_hunt_total, world.triforce_pieces_required[player].value)) triforce_pieces = min(90, max(treasure_hunt_total, world.worlds[player].options.triforce_pieces_required.value))
pieces_in_core = min(extraitems, triforce_pieces) pieces_in_core = min(extraitems, triforce_pieces)
additional_pieces_to_place = triforce_pieces - pieces_in_core additional_pieces_to_place = triforce_pieces - pieces_in_core
pool.extend(["Triforce Piece"] * pieces_in_core) pool.extend(["Triforce Piece"] * pieces_in_core)
extraitems -= pieces_in_core extraitems -= pieces_in_core
treasure_hunt_required = world.triforce_pieces_required[player].value treasure_hunt_required = world.worlds[player].options.triforce_pieces_required.value
for extra in diff.extras: for extra in diff.extras:
if extraitems >= len(extra): if extraitems >= len(extra):
@@ -714,10 +714,10 @@ def get_pool_core(world, player: int):
if retro_bow: if retro_bow:
replace = {'Single Arrow', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)', 'Arrow Upgrade (70)'} replace = {'Single Arrow', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)', 'Arrow Upgrade (70)'}
pool = ['Rupees (5)' if item in replace else item for item in pool] pool = ['Rupees (5)' if item in replace else item for item in pool]
if world.small_key_shuffle[player] == small_key_shuffle.option_universal: if world.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal:
pool.extend(diff.universal_keys) pool.extend(diff.universal_keys)
if mode == 'standard': if mode == 'standard':
if world.key_drop_shuffle[player]: if world.worlds[player].options.key_drop_shuffle:
key_locations = ['Secret Passage', 'Hyrule Castle - Map Guard Key Drop'] key_locations = ['Secret Passage', 'Hyrule Castle - Map Guard Key Drop']
key_location = world.random.choice(key_locations) key_location = world.random.choice(key_locations)
key_locations.remove(key_location) key_locations.remove(key_location)
@@ -741,11 +741,11 @@ def get_pool_core(world, player: int):
def make_custom_item_pool(world, player): def make_custom_item_pool(world, player):
shuffle = world.entrance_shuffle[player] shuffle = world.worlds[player].options.entrance_shuffle
difficulty = world.item_pool[player] difficulty = world.worlds[player].options.item_pool
timer = world.timer[player] timer = world.worlds[player].options.timer
goal = world.goal[player] goal = world.worlds[player].options.goal
mode = world.mode[player] mode = world.worlds[player].options.mode
customitemarray = world.customitemarray customitemarray = world.customitemarray
pool = [] pool = []
@@ -845,10 +845,10 @@ def make_custom_item_pool(world, player):
thisbottle = world.random.choice(diff.bottles) thisbottle = world.random.choice(diff.bottles)
pool.append(thisbottle) pool.append(thisbottle)
if "triforce" in world.goal[player]: if "triforce" in world.worlds[player].options.goal:
pool.extend(["Triforce Piece"] * world.triforce_pieces_available[player]) pool.extend(["Triforce Piece"] * world.worlds[player].options.triforce_pieces_available)
itemtotal += world.triforce_pieces_available[player] itemtotal += world.worlds[player].options.triforce_pieces_available
treasure_hunt_required = world.triforce_pieces_required[player] treasure_hunt_required = world.worlds[player].options.triforce_pieces_required
if timer in ['display', 'timed', 'timed_countdown']: if timer in ['display', 'timed', 'timed_countdown']:
clock_mode = 'countdown' if timer == 'timed_countdown' else 'stopwatch' clock_mode = 'countdown' if timer == 'timed_countdown' else 'stopwatch'
@@ -862,7 +862,7 @@ def make_custom_item_pool(world, player):
itemtotal = itemtotal + 1 itemtotal = itemtotal + 1
if mode == 'standard': if mode == 'standard':
if world.small_key_shuffle[player] == small_key_shuffle.option_universal: if world.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal:
key_location = world.random.choice( key_location = world.random.choice(
['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', ['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest',
'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross']) 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
@@ -885,9 +885,9 @@ def make_custom_item_pool(world, player):
pool.extend(['Magic Mirror'] * customitemarray[22]) pool.extend(['Magic Mirror'] * customitemarray[22])
pool.extend(['Moon Pearl'] * customitemarray[28]) pool.extend(['Moon Pearl'] * customitemarray[28])
if world.small_key_shuffle[player] == small_key_shuffle.option_universal: if world.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal:
itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in universal Mode itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in universal Mode
if world.key_drop_shuffle[player]: if world.worlds[player].options.key_drop_shuffle:
itemtotal = itemtotal - (len(key_drop_data) - 1) itemtotal = itemtotal - (len(key_drop_data) - 1)
if itemtotal < total_items_to_place: if itemtotal < total_items_to_place:
pool.extend(['Nothing'] * (total_items_to_place - itemtotal)) pool.extend(['Nothing'] * (total_items_to_place - itemtotal))

View File

@@ -11,11 +11,11 @@ def GetBeemizerItem(world, player: int, item):
return item return item
# first roll - replaceable item should be replaced, within beemizer_total_chance # first roll - replaceable item should be replaced, within beemizer_total_chance
if not world.beemizer_total_chance[player] or world.random.random() > (world.beemizer_total_chance[player] / 100): if not world.worlds[player].options.beemizer_total_chance or world.random.random() > (world.worlds[player].options.beemizer_total_chance / 100):
return item return item
# second roll - bee replacement should be trap, within beemizer_trap_chance # second roll - bee replacement should be trap, within beemizer_trap_chance
if not world.beemizer_trap_chance[player] or world.random.random() > (world.beemizer_trap_chance[player] / 100): if not world.worlds[player].options.beemizer_trap_chance or world.random.random() > (world.worlds[player].options.beemizer_trap_chance / 100):
return "Bee" if isinstance(item, str) else world.create_item("Bee", player) return "Bee" if isinstance(item, str) else world.create_item("Bee", player)
else: else:
return "Bee Trap" if isinstance(item, str) else world.create_item("Bee Trap", player) return "Bee Trap" if isinstance(item, str) else world.create_item("Bee Trap", player)

View File

@@ -156,10 +156,10 @@ class OpenPyramid(Choice):
def to_bool(self, world: MultiWorld, player: int) -> bool: def to_bool(self, world: MultiWorld, player: int) -> bool:
if self.value == self.option_goal: if self.value == self.option_goal:
return world.goal[player].current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} return world.worlds[player].options.goal.current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'}
elif self.value == self.option_auto: elif self.value == self.option_auto:
return world.goal[player].current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} \ return world.worlds[player].options.goal.current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} \
and (world.entrance_shuffle[player].current_key in {'vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'} or not and (world.worlds[player].options.entrance_shuffle.current_key in {'vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'} or not
world.shuffle_ganon) world.shuffle_ganon)
elif self.value == self.option_open: elif self.value == self.option_open:
return True return True

View File

@@ -220,14 +220,14 @@ def get_invalid_bunny_revival_dungeons():
def overworld_glitch_connections(world, player): def overworld_glitch_connections(world, player):
# Boots-accessible locations. # Boots-accessible locations.
create_owg_connections(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted')) create_owg_connections(player, world, get_boots_clip_exits_lw(world.worlds[player].options.mode == 'inverted'))
create_owg_connections(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted', player)) create_owg_connections(player, world, get_boots_clip_exits_dw(world.worlds[player].options.mode == 'inverted', player))
# Glitched speed drops. # Glitched speed drops.
create_owg_connections(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted')) create_owg_connections(player, world, get_glitched_speed_drops_dw(world.worlds[player].options.mode == 'inverted'))
# Mirror clip spots. # Mirror clip spots.
if world.mode[player] != 'inverted': if world.worlds[player].options.mode != 'inverted':
create_owg_connections(player, world, get_mirror_clip_spots_dw()) create_owg_connections(player, world, get_mirror_clip_spots_dw())
create_owg_connections(player, world, get_mirror_offset_spots_dw()) create_owg_connections(player, world, get_mirror_offset_spots_dw())
else: else:
@@ -237,24 +237,24 @@ def overworld_glitch_connections(world, player):
def overworld_glitches_rules(world, player): def overworld_glitches_rules(world, player):
# Boots-accessible locations. # Boots-accessible locations.
set_owg_connection_rules(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted'), lambda state: can_boots_clip_lw(state, player)) set_owg_connection_rules(player, world, get_boots_clip_exits_lw(world.worlds[player].options.mode == 'inverted'), lambda state: can_boots_clip_lw(state, player))
set_owg_connection_rules(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted', player), lambda state: can_boots_clip_dw(state, player)) set_owg_connection_rules(player, world, get_boots_clip_exits_dw(world.worlds[player].options.mode == 'inverted', player), lambda state: can_boots_clip_dw(state, player))
# Glitched speed drops. # Glitched speed drops.
set_owg_connection_rules(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted'), lambda state: can_get_glitched_speed_dw(state, player)) set_owg_connection_rules(player, world, get_glitched_speed_drops_dw(world.worlds[player].options.mode == 'inverted'), lambda state: can_get_glitched_speed_dw(state, player))
# Dark Death Mountain Ledge Clip Spot also accessible with mirror. # Dark Death Mountain Ledge Clip Spot also accessible with mirror.
if world.mode[player] != 'inverted': if world.worlds[player].options.mode != 'inverted':
add_alternate_rule(world.get_entrance('Dark Death Mountain Ledge Clip Spot', player), lambda state: state.has('Magic Mirror', player)) add_alternate_rule(world.get_entrance('Dark Death Mountain Ledge Clip Spot', player), lambda state: state.has('Magic Mirror', player))
# Mirror clip spots. # Mirror clip spots.
if world.mode[player] != 'inverted': if world.worlds[player].options.mode != 'inverted':
set_owg_connection_rules(player, world, get_mirror_clip_spots_dw(), lambda state: state.has('Magic Mirror', player)) set_owg_connection_rules(player, world, get_mirror_clip_spots_dw(), lambda state: state.has('Magic Mirror', player))
set_owg_connection_rules(player, world, get_mirror_offset_spots_dw(), lambda state: state.has('Magic Mirror', player) and can_boots_clip_lw(state, player)) set_owg_connection_rules(player, world, get_mirror_offset_spots_dw(), lambda state: state.has('Magic Mirror', player) and can_boots_clip_lw(state, player))
else: else:
set_owg_connection_rules(player, world, get_mirror_offset_spots_lw(player), lambda state: state.has('Magic Mirror', player) and can_boots_clip_dw(state, player)) set_owg_connection_rules(player, world, get_mirror_offset_spots_lw(player), lambda state: state.has('Magic Mirror', player) and can_boots_clip_dw(state, player))
# Regions that require the boots and some other stuff. # Regions that require the boots and some other stuff.
if world.mode[player] != 'inverted': if world.worlds[player].options.mode != 'inverted':
world.get_entrance('Turtle Rock Teleporter', player).access_rule = lambda state: (can_boots_clip_lw(state, player) or can_lift_heavy_rocks(state, player)) and state.has('Hammer', player) world.get_entrance('Turtle Rock Teleporter', player).access_rule = lambda state: (can_boots_clip_lw(state, player) or can_lift_heavy_rocks(state, player)) and state.has('Hammer', player)
add_alternate_rule(world.get_entrance('Waterfall of Wishing', player), lambda state: state.has('Moon Pearl', player) or state.has('Pegasus Boots', player)) add_alternate_rule(world.get_entrance('Waterfall of Wishing', player), lambda state: state.has('Moon Pearl', player) or state.has('Pegasus Boots', player))
else: else:

View File

@@ -92,7 +92,7 @@ class LocalRom:
# cause crash to provide traceback # cause crash to provide traceback
import xxtea import xxtea
local_random = world.per_slot_randoms[player] local_random = world.worlds[player].random
key = bytes(local_random.getrandbits(8 * 16).to_bytes(16, 'big')) key = bytes(local_random.getrandbits(8 * 16).to_bytes(16, 'big'))
self.write_bytes(0x1800B0, bytearray(key)) self.write_bytes(0x1800B0, bytearray(key))
self.write_int16(0x180087, 1) self.write_int16(0x180087, 1)
@@ -281,7 +281,6 @@ def apply_random_sprite_on_event(rom: LocalRom, sprite, local_random, allow_rand
def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory): def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory):
player = world.player player = world.player
multiworld = world.multiworld
check_enemizer(enemizercli) check_enemizer(enemizercli)
randopatch_path = os.path.abspath(os.path.join(output_directory, f'enemizer_randopatch_{player}.sfc')) randopatch_path = os.path.abspath(os.path.join(output_directory, f'enemizer_randopatch_{player}.sfc'))
options_path = os.path.abspath(os.path.join(output_directory, f'enemizer_options_{player}.json')) options_path = os.path.abspath(os.path.join(output_directory, f'enemizer_options_{player}.json'))
@@ -289,18 +288,18 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory):
# write options file for enemizer # write options file for enemizer
options = { options = {
'RandomizeEnemies': multiworld.enemy_shuffle[player].value, 'RandomizeEnemies': world.options.enemy_shuffle.value,
'RandomizeEnemiesType': 3, 'RandomizeEnemiesType': 3,
'RandomizeBushEnemyChance': multiworld.bush_shuffle[player].value, 'RandomizeBushEnemyChance': world.options.bush_shuffle.value,
'RandomizeEnemyHealthRange': multiworld.enemy_health[player] != 'default', 'RandomizeEnemyHealthRange': world.options.enemy_health != 'default',
'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[ 'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[
multiworld.enemy_health[player].current_key], world.options.enemy_health.current_key],
'OHKO': False, 'OHKO': False,
'RandomizeEnemyDamage': multiworld.enemy_damage[player] != 'default', 'RandomizeEnemyDamage': world.options.enemy_damage != 'default',
'AllowEnemyZeroDamage': True, 'AllowEnemyZeroDamage': True,
'ShuffleEnemyDamageGroups': multiworld.enemy_damage[player] != 'default', 'ShuffleEnemyDamageGroups': world.options.enemy_damage != 'default',
'EnemyDamageChaosMode': multiworld.enemy_damage[player] == 'chaos', 'EnemyDamageChaosMode': world.options.enemy_damage == 'chaos',
'EasyModeEscape': multiworld.mode[player] == "standard", 'EasyModeEscape': world.options.mode == "standard",
'EnemiesAbsorbable': False, 'EnemiesAbsorbable': False,
'AbsorbableSpawnRate': 10, 'AbsorbableSpawnRate': 10,
'AbsorbableTypes': { 'AbsorbableTypes': {
@@ -329,7 +328,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory):
'GrayscaleMode': False, 'GrayscaleMode': False,
'GenerateSpoilers': False, 'GenerateSpoilers': False,
'RandomizeLinkSpritePalette': False, 'RandomizeLinkSpritePalette': False,
'RandomizePots': multiworld.pot_shuffle[player].value, 'RandomizePots': world.options.pot_shuffle.value,
'ShuffleMusic': False, 'ShuffleMusic': False,
'BootlegMagic': True, 'BootlegMagic': True,
'CustomBosses': False, 'CustomBosses': False,
@@ -342,7 +341,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory):
'BeesLevel': 0, 'BeesLevel': 0,
'RandomizeTileTrapPattern': False, 'RandomizeTileTrapPattern': False,
'RandomizeTileTrapFloorTile': False, 'RandomizeTileTrapFloorTile': False,
'AllowKillableThief': multiworld.killable_thieves[player].value, 'AllowKillableThief': world.options.killable_thieves.value,
'RandomizeSpriteOnHit': False, 'RandomizeSpriteOnHit': False,
'DebugMode': False, 'DebugMode': False,
'DebugForceEnemy': False, 'DebugForceEnemy': False,
@@ -366,13 +365,13 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory):
'MiseryMire': world.dungeons["Misery Mire"].boss.enemizer_name, 'MiseryMire': world.dungeons["Misery Mire"].boss.enemizer_name,
'TurtleRock': world.dungeons["Turtle Rock"].boss.enemizer_name, 'TurtleRock': world.dungeons["Turtle Rock"].boss.enemizer_name,
'GanonsTower1': 'GanonsTower1':
world.dungeons["Ganons Tower" if multiworld.mode[player] != 'inverted' else world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else
"Inverted Ganons Tower"].bosses['bottom'].enemizer_name, "Inverted Ganons Tower"].bosses['bottom'].enemizer_name,
'GanonsTower2': 'GanonsTower2':
world.dungeons["Ganons Tower" if multiworld.mode[player] != 'inverted' else world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else
"Inverted Ganons Tower"].bosses['middle'].enemizer_name, "Inverted Ganons Tower"].bosses['middle'].enemizer_name,
'GanonsTower3': 'GanonsTower3':
world.dungeons["Ganons Tower" if multiworld.mode[player] != 'inverted' else world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else
"Inverted Ganons Tower"].bosses['top'].enemizer_name, "Inverted Ganons Tower"].bosses['top'].enemizer_name,
'GanonsTower4': 'Agahnim2', 'GanonsTower4': 'Agahnim2',
'Ganon': 'Ganon', 'Ganon': 'Ganon',
@@ -386,7 +385,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory):
max_enemizer_tries = 5 max_enemizer_tries = 5
for i in range(max_enemizer_tries): for i in range(max_enemizer_tries):
enemizer_seed = str(multiworld.per_slot_randoms[player].randint(0, 999999999)) enemizer_seed = str(world.random.randint(0, 999999999))
enemizer_command = [os.path.abspath(enemizercli), enemizer_command = [os.path.abspath(enemizercli),
'--rom', randopatch_path, '--rom', randopatch_path,
'--seed', enemizer_seed, '--seed', enemizer_seed,
@@ -416,7 +415,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory):
continue continue
for j in range(i + 1, max_enemizer_tries): for j in range(i + 1, max_enemizer_tries):
multiworld.per_slot_randoms[player].randint(0, 999999999) world.random.randint(0, 999999999)
# Sacrifice all remaining random numbers that would have been used for unused enemizer tries. # Sacrifice all remaining random numbers that would have been used for unused enemizer tries.
# This allows for future enemizer bug fixes to NOT affect the rest of the seed's randomness # This allows for future enemizer bug fixes to NOT affect the rest of the seed's randomness
break break
@@ -430,7 +429,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory):
# Moblins attached to "key drop" locations crash the game when dropping their item when Key Drop Shuffle is on. # Moblins attached to "key drop" locations crash the game when dropping their item when Key Drop Shuffle is on.
# Replace them with a Slime enemy if they are placed. # Replace them with a Slime enemy if they are placed.
if multiworld.key_drop_shuffle[player]: if world.options.key_drop_shuffle:
key_drop_enemies = { key_drop_enemies = {
0x4DA20, 0x4DA5C, 0x4DB7F, 0x4DD73, 0x4DDC3, 0x4DE07, 0x4E201, 0x4DA20, 0x4DA5C, 0x4DB7F, 0x4DD73, 0x4DDC3, 0x4DE07, 0x4E201,
0x4E20A, 0x4E326, 0x4E4F7, 0x4E687, 0x4E70C, 0x4E7C8, 0x4E7FA 0x4E20A, 0x4E326, 0x4E4F7, 0x4E687, 0x4E70C, 0x4E7C8, 0x4E7FA
@@ -792,8 +791,8 @@ def get_nonnative_item_sprite(code: int) -> int:
def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
local_random = world.worlds[player].random
local_world = world.worlds[player] local_world = world.worlds[player]
local_random = local_world.random
# patch items # patch items
@@ -840,14 +839,14 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
# patch music # patch music
music_addresses = dungeon_music_addresses[location.name] music_addresses = dungeon_music_addresses[location.name]
if world.map_shuffle[player]: if local_world.options.map_shuffle:
music = local_random.choice([0x11, 0x16]) music = local_random.choice([0x11, 0x16])
else: else:
music = 0x11 if 'Pendant' in location.item.name else 0x16 music = 0x11 if 'Pendant' in location.item.name else 0x16
for music_address in music_addresses: for music_address in music_addresses:
rom.write_byte(music_address, music) rom.write_byte(music_address, music)
if world.map_shuffle[player]: if local_world.options.map_shuffle:
rom.write_byte(0x155C9, local_random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle rom.write_byte(0x155C9, local_random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
# patch entrance/exits/holes # patch entrance/exits/holes
@@ -868,15 +867,15 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
# Thanks to Zarby89 for originally finding these values # Thanks to Zarby89 for originally finding these values
# todo fix screen scrolling # todo fix screen scrolling
if world.entrance_shuffle[player] != 'insanity' and \ if local_world.options.entrance_shuffle != 'insanity' and \
exit.name in {'Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit', exit.name in {'Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit',
'Skull Woods Final Section Exit', 'Ice Palace Exit', 'Misery Mire Exit', 'Skull Woods Final Section Exit', 'Ice Palace Exit', 'Misery Mire Exit',
'Palace of Darkness Exit', 'Swamp Palace Exit', 'Ganons Tower Exit', 'Palace of Darkness Exit', 'Swamp Palace Exit', 'Ganons Tower Exit',
'Desert Palace Exit (North)', 'Agahnims Tower Exit', 'Spiral Cave Exit (Top)', 'Desert Palace Exit (North)', 'Agahnims Tower Exit', 'Spiral Cave Exit (Top)',
'Superbunny Cave Exit (Bottom)', 'Turtle Rock Ledge Exit (East)'} and \ 'Superbunny Cave Exit (Bottom)', 'Turtle Rock Ledge Exit (East)'} and \
(world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic'] or (local_world.options.glitches_required not in ['hybrid_major_glitches', 'no_logic'] or
exit.name not in {'Palace of Darkness Exit', 'Tower of Hera Exit', 'Swamp Palace Exit'}): exit.name not in {'Palace of Darkness Exit', 'Tower of Hera Exit', 'Swamp Palace Exit'}):
# For exits that connot be reached from another, no need to apply offset fixes. # For exits that cannot be reached from another, no need to apply offset fixes.
rom.write_int16(0x15DB5 + 2 * offset, link_y) # same as final else rom.write_int16(0x15DB5 + 2 * offset, link_y) # same as final else
elif room_id == 0x0059 and local_world.fix_skullwoods_exit: elif room_id == 0x0059 and local_world.fix_skullwoods_exit:
rom.write_int16(0x15DB5 + 2 * offset, 0x00F8) rom.write_int16(0x15DB5 + 2 * offset, 0x00F8)
@@ -903,7 +902,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
else: else:
# patch door table # patch door table
rom.write_byte(0xDBB73 + exit.addresses, exit.target) rom.write_byte(0xDBB73 + exit.addresses, exit.target)
if world.mode[player] == 'inverted': if local_world.options.mode == 'inverted':
patch_shuffled_dark_sanc(world, rom, player) patch_shuffled_dark_sanc(world, rom, player)
write_custom_shops(rom, world, player) write_custom_shops(rom, world, player)
@@ -914,16 +913,16 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
return 0x53 + int(num), 0x79 + int(num) return 0x53 + int(num), 0x79 + int(num)
credits_total = 216 credits_total = 216
if world.retro_caves[player]: # Old man cave and Take any caves will count towards collection rate. if local_world.options.retro_caves: # Old man cave and Take any caves will count towards collection rate.
credits_total += 5 credits_total += 5
if world.shop_item_slots[player]: # Potion shop only counts towards collection rate if included in the shuffle. if local_world.options.shop_item_slots: # Potion shop only counts towards collection rate if included in the shuffle.
credits_total += 30 if world.include_witch_hut[player] else 27 credits_total += 30 if local_world.options.include_witch_hut else 27
if world.shuffle_capacity_upgrades[player]: if local_world.options.shuffle_capacity_upgrades:
credits_total += 2 credits_total += 2
rom.write_byte(0x187010, credits_total) # dynamic credits rom.write_byte(0x187010, credits_total) # dynamic credits
if world.key_drop_shuffle[player]: if local_world.options.key_drop_shuffle:
rom.write_byte(0x140000, 1) # enable key drop shuffle rom.write_byte(0x140000, 1) # enable key drop shuffle
credits_total += len(key_drop_data) credits_total += len(key_drop_data)
# update dungeon counters # update dungeon counters
@@ -977,11 +976,11 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_byte(0x51DE, 0x00) rom.write_byte(0x51DE, 0x00)
# set open mode: # set open mode:
if world.mode[player] in ['open', 'inverted']: if local_world.options.mode in ['open', 'inverted']:
rom.write_byte(0x180032, 0x01) # open mode rom.write_byte(0x180032, 0x01) # open mode
if world.mode[player] == 'inverted': if local_world.options.mode == 'inverted':
set_inverted_mode(world, player, rom) set_inverted_mode(world, player, rom)
elif world.mode[player] == 'standard': elif local_world.options.mode == 'standard':
rom.write_byte(0x180032, 0x00) # standard mode rom.write_byte(0x180032, 0x00) # standard mode
uncle_location = world.get_location('Link\'s Uncle', player) uncle_location = world.get_location('Link\'s Uncle', player)
@@ -1001,7 +1000,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_bytes(0x6D323, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E]) rom.write_bytes(0x6D323, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
# set light cones # set light cones
rom.write_byte(0x180038, 0x01 if world.mode[player] == "standard" else 0x00) rom.write_byte(0x180038, 0x01 if local_world.options.mode == "standard" else 0x00)
rom.write_byte(0x180039, 0x01 if world.light_world_light_cone else 0x00) rom.write_byte(0x180039, 0x01 if world.light_world_light_cone else 0x00)
rom.write_byte(0x18003A, 0x01 if world.dark_world_light_cone else 0x00) rom.write_byte(0x18003A, 0x01 if world.dark_world_light_cone else 0x00)
@@ -1011,7 +1010,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on
# handle item_functionality # handle item_functionality
if world.item_functionality[player] == 'hard': if local_world.options.item_functionality == 'hard':
rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon
rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup
# Powdered Fairies Prize # Powdered Fairies Prize
@@ -1031,7 +1030,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_int16(0x180036, world.rupoor_cost) rom.write_int16(0x180036, world.rupoor_cost)
# Set stun items # Set stun items
rom.write_byte(0x180180, 0x02) # Hookshot only rom.write_byte(0x180180, 0x02) # Hookshot only
elif world.item_functionality[player] == 'expert': elif local_world.options.item_functionality == 'expert':
rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon
rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup
# Powdered Fairies Prize # Powdered Fairies Prize
@@ -1071,7 +1070,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
# Set stun items # Set stun items
rom.write_byte(0x180180, 0x03) # All standard items rom.write_byte(0x180180, 0x03) # All standard items
# Set overflow items for progressive equipment # Set overflow items for progressive equipment
if world.timer[player] in ['timed', 'timed_countdown', 'timed_ohko']: if local_world.options.timer in ['timed', 'timed_countdown', 'timed_ohko']:
overflow_replacement = GREEN_CLOCK overflow_replacement = GREEN_CLOCK
else: else:
overflow_replacement = GREEN_TWENTY_RUPEES overflow_replacement = GREEN_TWENTY_RUPEES
@@ -1083,7 +1082,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
# Set overflow items for progressive equipment # Set overflow items for progressive equipment
rom.write_bytes(0x180090, rom.write_bytes(0x180090,
[difficulty.progressive_sword_limit if not world.swordless[player] else 0, [difficulty.progressive_sword_limit if not local_world.options.swordless else 0,
item_table[difficulty.basicsword[-1]].item_code, item_table[difficulty.basicsword[-1]].item_code,
difficulty.progressive_shield_limit, item_table[difficulty.basicshield[-1]].item_code, difficulty.progressive_shield_limit, item_table[difficulty.basicshield[-1]].item_code,
difficulty.progressive_armor_limit, item_table[difficulty.basicarmor[-1]].item_code, difficulty.progressive_armor_limit, item_table[difficulty.basicarmor[-1]].item_code,
@@ -1091,7 +1090,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
difficulty.progressive_bow_limit, item_table[difficulty.basicbow[-1]].item_code]) difficulty.progressive_bow_limit, item_table[difficulty.basicbow[-1]].item_code])
if difficulty.progressive_bow_limit < 2 and ( if difficulty.progressive_bow_limit < 2 and (
world.swordless[player] or world.glitches_required[player] == 'no_glitches'): local_world.options.swordless or local_world.options.glitches_required == 'no_glitches'):
rom.write_bytes(0x180098, [2, item_table["Silver Bow"].item_code]) rom.write_bytes(0x180098, [2, item_table["Silver Bow"].item_code])
rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon
rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup
@@ -1099,15 +1098,15 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
# set up game internal RNG seed # set up game internal RNG seed
rom.write_bytes(0x178000, local_random.getrandbits(8 * 1024).to_bytes(1024, 'big')) rom.write_bytes(0x178000, local_random.getrandbits(8 * 1024).to_bytes(1024, 'big'))
prize_replacements = {} prize_replacements = {}
if world.item_functionality[player] in ['hard', 'expert']: if local_world.options.item_functionality in ['hard', 'expert']:
prize_replacements[0xE0] = 0xDF # Fairy -> heart prize_replacements[0xE0] = 0xDF # Fairy -> heart
prize_replacements[0xE3] = 0xD8 # Big magic -> small magic prize_replacements[0xE3] = 0xD8 # Big magic -> small magic
if world.retro_bow[player]: if local_world.options.retro_bow:
prize_replacements[0xE1] = 0xDA # 5 Arrows -> Blue Rupee prize_replacements[0xE1] = 0xDA # 5 Arrows -> Blue Rupee
prize_replacements[0xE2] = 0xDB # 10 Arrows -> Red Rupee prize_replacements[0xE2] = 0xDB # 10 Arrows -> Red Rupee
if world.shuffle_prizes[player] in ("general", "both"): if local_world.options.shuffle_prizes in ("general", "both"):
# shuffle prize packs # shuffle prize packs
prizes = [0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD8, 0xD8, 0xD9, 0xDA, 0xD9, 0xDA, 0xDB, 0xDA, 0xD9, 0xDA, 0xDA, 0xE0, prizes = [0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD8, 0xD8, 0xD9, 0xDA, 0xD9, 0xDA, 0xDB, 0xDA, 0xD9, 0xDA, 0xDA, 0xE0,
0xDF, 0xDF, 0xDA, 0xE0, 0xDF, 0xD8, 0xDF, 0xDF, 0xDF, 0xDA, 0xE0, 0xDF, 0xD8, 0xDF,
@@ -1169,7 +1168,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
byte = int(rom.read_byte(address)) byte = int(rom.read_byte(address))
rom.write_byte(address, prize_replacements.get(byte, byte)) rom.write_byte(address, prize_replacements.get(byte, byte))
if world.shuffle_prizes[player] in ("bonk", "both"): if local_world.options.shuffle_prizes in ("bonk", "both"):
# set bonk prizes # set bonk prizes
bonk_prizes = [0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xAC, 0xE3, 0xE3, 0xDA, 0xE3, 0xDA, 0xD8, 0xAC, bonk_prizes = [0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xAC, 0xE3, 0xE3, 0xDA, 0xE3, 0xDA, 0xD8, 0xAC,
0xAC, 0xE3, 0xD8, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xDC, 0xDB, 0xE3, 0xDA, 0x79, 0x79, 0xAC, 0xE3, 0xD8, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xDC, 0xDB, 0xE3, 0xDA, 0x79, 0x79,
@@ -1196,7 +1195,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
0x12, 0x01, 0x35, 0xFF, # lamp -> 5 rupees 0x12, 0x01, 0x35, 0xFF, # lamp -> 5 rupees
0x51, 0x06, 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade 0x51, 0x06, 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade
0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade 0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade
0x58, 0x01, 0x36 if world.retro_bow[player] else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode) 0x58, 0x01, 0x36 if local_world.options.retro_bow else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode)
0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20 0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20
0x17, difficulty.heart_piece_limit, 0x47, 0xff, # piece of heart -> green 20 0x17, difficulty.heart_piece_limit, 0x47, 0xff, # piece of heart -> green 20
0xFF, 0xFF, 0xFF, 0xFF, # end of table sentinel 0xFF, 0xFF, 0xFF, 0xFF, # end of table sentinel
@@ -1238,13 +1237,13 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_byte(0x180029, 0x01) # Smithy quick item give rom.write_byte(0x180029, 0x01) # Smithy quick item give
# set swordless mode settings # set swordless mode settings
rom.write_byte(0x18003F, 0x01 if world.swordless[player] else 0x00) # hammer can harm ganon rom.write_byte(0x18003F, 0x01 if local_world.options.swordless else 0x00) # hammer can harm ganon
rom.write_byte(0x180040, 0x01 if world.swordless[player] else 0x00) # open curtains rom.write_byte(0x180040, 0x01 if local_world.options.swordless else 0x00) # open curtains
rom.write_byte(0x180041, 0x01 if world.swordless[player] else 0x00) # swordless medallions rom.write_byte(0x180041, 0x01 if local_world.options.swordless else 0x00) # swordless medallions
rom.write_byte(0x180043, 0xFF if world.swordless[player] else 0x00) # starting sword for link rom.write_byte(0x180043, 0xFF if local_world.options.swordless else 0x00) # starting sword for link
rom.write_byte(0x180044, 0x01 if world.swordless[player] else 0x00) # hammer activates tablets rom.write_byte(0x180044, 0x01 if local_world.options.swordless else 0x00) # hammer activates tablets
if world.item_functionality[player] == 'easy': if local_world.options.item_functionality == 'easy':
rom.write_byte(0x18003F, 0x01) # hammer can harm ganon rom.write_byte(0x18003F, 0x01) # hammer can harm ganon
rom.write_byte(0x180041, 0x02) # Allow swordless medallion use EVERYWHERE. rom.write_byte(0x180041, 0x02) # Allow swordless medallion use EVERYWHERE.
rom.write_byte(0x180044, 0x01) # hammer activates tablets rom.write_byte(0x180044, 0x01) # hammer activates tablets
@@ -1262,11 +1261,11 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
# Set up requested clock settings # Set up requested clock settings
if local_world.clock_mode in ['countdown-ohko', 'stopwatch', 'countdown']: if local_world.clock_mode in ['countdown-ohko', 'stopwatch', 'countdown']:
rom.write_int32(0x180200, rom.write_int32(0x180200,
world.red_clock_time[player] * 60 * 60) # red clock adjustment time (in frames, sint32) local_world.options.red_clock_time * 60 * 60) # red clock adjustment time (in frames, sint32)
rom.write_int32(0x180204, rom.write_int32(0x180204,
world.blue_clock_time[player] * 60 * 60) # blue clock adjustment time (in frames, sint32) local_world.options.blue_clock_time * 60 * 60) # blue clock adjustment time (in frames, sint32)
rom.write_int32(0x180208, rom.write_int32(0x180208,
world.green_clock_time[player] * 60 * 60) # green clock adjustment time (in frames, sint32) local_world.options.green_clock_time * 60 * 60) # green clock adjustment time (in frames, sint32)
else: else:
rom.write_int32(0x180200, 0) # red clock adjustment time (in frames, sint32) rom.write_int32(0x180200, 0) # red clock adjustment time (in frames, sint32)
rom.write_int32(0x180204, 0) # blue clock adjustment time (in frames, sint32) rom.write_int32(0x180204, 0) # blue clock adjustment time (in frames, sint32)
@@ -1274,7 +1273,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
# Set up requested start time for countdown modes # Set up requested start time for countdown modes
if local_world.clock_mode in ['countdown-ohko', 'countdown']: if local_world.clock_mode in ['countdown-ohko', 'countdown']:
rom.write_int32(0x18020C, world.countdown_start_time[player] * 60 * 60) # starting time (in frames, sint32) rom.write_int32(0x18020C, local_world.options.countdown_start_time * 60 * 60) # starting time (in frames, sint32)
else: else:
rom.write_int32(0x18020C, 0) # starting time (in frames, sint32) rom.write_int32(0x18020C, 0) # starting time (in frames, sint32)
@@ -1287,7 +1286,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed
gametype = 0x04 # item gametype = 0x04 # item
if world.entrance_shuffle[player] != 'vanilla': if local_world.options.entrance_shuffle != 'vanilla':
gametype |= 0x02 # entrance gametype |= 0x02 # entrance
if enemized: if enemized:
gametype |= 0x01 # enemizer gametype |= 0x01 # enemizer
@@ -1298,7 +1297,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_byte(0x1800A2, 0x01 if local_world.fix_fake_world else 0x00) rom.write_byte(0x1800A2, 0x01 if local_world.fix_fake_world else 0x00)
# Lock or unlock aga tower door during escape sequence. # Lock or unlock aga tower door during escape sequence.
rom.write_byte(0x180169, 0x00) rom.write_byte(0x180169, 0x00)
if world.mode[player] == 'inverted': if local_world.options.mode == 'inverted':
rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted
rom.write_byte(0x180171, rom.write_byte(0x180171,
0x01 if local_world.ganon_at_pyramid else 0x00) # Enable respawning on pyramid after ganon death 0x01 if local_world.ganon_at_pyramid else 0x00) # Enable respawning on pyramid after ganon death
@@ -1309,9 +1308,8 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest
rom.write_byte(0x50599, 0x00) # disable below ganon chest rom.write_byte(0x50599, 0x00) # disable below ganon chest
rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest
rom.write_byte(0x18008B, 0x01 if world.open_pyramid[player].to_bool(world, player) else 0x00) # pre-open Pyramid Hole rom.write_byte(0x18008B, 0x01 if local_world.options.open_pyramid.to_bool(world, player) else 0x00) # pre-open Pyramid Hole
rom.write_byte(0x18008C, 0x01 if world.crystals_needed_for_gt[ rom.write_byte(0x18008C, 0x01 if local_world.options.crystals_needed_for_gt == 0 else 0x00) # GT pre-opened if crystal requirement is 0
player] == 0 else 0x00) # GT pre-opened if crystal requirement is 0
rom.write_byte(0xF5D73, 0xF0) # bees are catchable rom.write_byte(0xF5D73, 0xF0) # bees are catchable
rom.write_byte(0xF5F10, 0xF0) # bees are catchable rom.write_byte(0xF5F10, 0xF0) # bees are catchable
rom.write_byte(0x180086, 0x00 if world.aga_randomness else 0x01) # set blue ball and ganon warp randomness rom.write_byte(0x180086, 0x00 if world.aga_randomness else 0x01) # set blue ball and ganon warp randomness
@@ -1325,7 +1323,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
equip[0x36C] = 0x18 equip[0x36C] = 0x18
equip[0x36D] = 0x18 equip[0x36D] = 0x18
equip[0x379] = 0x68 equip[0x379] = 0x68
starting_max_bombs = 0 if world.bombless_start[player] else 10 starting_max_bombs = 0 if local_world.options.bombless_start else 10
starting_max_arrows = 30 starting_max_arrows = 30
startingstate = CollectionState(world) startingstate = CollectionState(world)
@@ -1333,12 +1331,12 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
if startingstate.has('Silver Bow', player): if startingstate.has('Silver Bow', player):
equip[0x340] = 1 equip[0x340] = 1
equip[0x38E] |= 0x60 equip[0x38E] |= 0x60
if not world.retro_bow[player]: if not local_world.options.retro_bow:
equip[0x38E] |= 0x80 equip[0x38E] |= 0x80
elif startingstate.has('Bow', player): elif startingstate.has('Bow', player):
equip[0x340] = 1 equip[0x340] = 1
equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases
if not world.retro_bow[player]: if not local_world.options.retro_bow:
equip[0x38E] |= 0x80 equip[0x38E] |= 0x80
if startingstate.has('Silver Arrows', player): if startingstate.has('Silver Arrows', player):
equip[0x38E] |= 0x40 equip[0x38E] |= 0x40
@@ -1476,7 +1474,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
elif item.name in bombs: elif item.name in bombs:
equip[0x343] += bombs[item.name] equip[0x343] += bombs[item.name]
elif item.name in arrows: elif item.name in arrows:
if world.retro_bow[player]: if local_world.options.retro_bow:
equip[0x38E] |= 0x80 equip[0x38E] |= 0x80
equip[0x377] = 1 equip[0x377] = 1
else: else:
@@ -1502,16 +1500,13 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_bytes(0x183000, equip[0x340:]) rom.write_bytes(0x183000, equip[0x340:])
rom.write_bytes(0x271A6, equip[0x340:0x340 + 60]) rom.write_bytes(0x271A6, equip[0x340:0x340 + 60])
rom.write_byte(0x18004A, 0x00 if world.mode[player] != 'inverted' else 0x01) # Inverted mode rom.write_byte(0x18004A, 0x00 if local_world.options.mode != 'inverted' else 0x01) # Inverted mode
rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier
rom.write_byte(0x2AF79, 0xD0 if world.mode[ rom.write_byte(0x2AF79, 0xD0 if local_world.options.mode != 'inverted' else 0xF0) # vortexes: Normal (D0=light to dark, F0=dark to light, 42 = both)
player] != 'inverted' else 0xF0) # vortexes: Normal (D0=light to dark, F0=dark to light, 42 = both) rom.write_byte(0x3A943, 0xD0 if local_world.options.mode != 'inverted' else 0xF0) # Mirror: Normal (D0=Dark to Light, F0=light to dark, 42 = both)
rom.write_byte(0x3A943, 0xD0 if world.mode[ rom.write_byte(0x3A96D, 0xF0 if local_world.options.mode != 'inverted' else 0xD0) # Residual Portal: Normal (F0= Light Side, D0=Dark Side, 42 = both (Darth Vader))
player] != 'inverted' else 0xF0) # Mirror: Normal (D0=Dark to Light, F0=light to dark, 42 = both)
rom.write_byte(0x3A96D, 0xF0 if world.mode[
player] != 'inverted' else 0xD0) # Residual Portal: Normal (F0= Light Side, D0=Dark Side, 42 = both (Darth Vader))
rom.write_byte(0x3A9A7, 0xD0) # Residual Portal: Normal (D0= Light Side, F0=Dark Side, 42 = both (Darth Vader)) rom.write_byte(0x3A9A7, 0xD0) # Residual Portal: Normal (D0= Light Side, F0=Dark Side, 42 = both (Darth Vader))
if world.shuffle_capacity_upgrades[player]: if local_world.options.shuffle_capacity_upgrades:
rom.write_bytes(0x180080, rom.write_bytes(0x180080,
[5, 10, 5, 10]) # values to fill for Capacity Upgrades (Bomb5, Bomb10, Arrow5, Arrow10) [5, 10, 5, 10]) # values to fill for Capacity Upgrades (Bomb5, Bomb10, Arrow5, Arrow10)
else: else:
@@ -1522,21 +1517,21 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
(0x02 if 'bombs' in local_world.escape_assist else 0x00) | (0x02 if 'bombs' in local_world.escape_assist else 0x00) |
(0x04 if 'magic' in local_world.escape_assist else 0x00))) # Escape assist (0x04 if 'magic' in local_world.escape_assist else 0x00))) # Escape assist
if world.goal[player] in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']: if local_world.options.goal in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']:
rom.write_byte(0x18003E, 0x01) # make ganon invincible rom.write_byte(0x18003E, 0x01) # make ganon invincible
elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: elif local_world.options.goal in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
rom.write_byte(0x18003E, 0x05) # make ganon invincible until enough triforce pieces are collected rom.write_byte(0x18003E, 0x05) # make ganon invincible until enough triforce pieces are collected
elif world.goal[player] in ['ganon_pedestal']: elif local_world.options.goal in ['ganon_pedestal']:
rom.write_byte(0x18003E, 0x06) rom.write_byte(0x18003E, 0x06)
elif world.goal[player] in ['bosses']: elif local_world.options.goal in ['bosses']:
rom.write_byte(0x18003E, 0x02) # make ganon invincible until all bosses are beat rom.write_byte(0x18003E, 0x02) # make ganon invincible until all bosses are beat
elif world.goal[player] in ['crystals']: elif local_world.options.goal in ['crystals']:
rom.write_byte(0x18003E, 0x04) # make ganon invincible until all crystals rom.write_byte(0x18003E, 0x04) # make ganon invincible until all crystals
else: else:
rom.write_byte(0x18003E, 0x03) # make ganon invincible until all crystals and aga 2 are collected rom.write_byte(0x18003E, 0x03) # make ganon invincible until all crystals and aga 2 are collected
rom.write_byte(0x18005E, world.crystals_needed_for_gt[player]) rom.write_byte(0x18005E, local_world.options.crystals_needed_for_gt)
rom.write_byte(0x18005F, world.crystals_needed_for_ganon[player]) rom.write_byte(0x18005F, local_world.options.crystals_needed_for_ganon)
# Bitfield - enable text box to show with free roaming items # Bitfield - enable text box to show with free roaming items
# #
@@ -1547,21 +1542,20 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
# c - enabled for inside compasses # c - enabled for inside compasses
# s - enabled for inside small keys # s - enabled for inside small keys
# block HC upstairs doors in rain state in standard mode # block HC upstairs doors in rain state in standard mode
rom.write_byte(0x18008A, 0x01 if world.mode[player] == "standard" and world.entrance_shuffle[player] != 'vanilla' else 0x00) rom.write_byte(0x18008A, 0x01 if local_world.options.mode == "standard" and local_world.options.entrance_shuffle != 'vanilla' else 0x00)
rom.write_byte(0x18016A, 0x10 | ((0x01 if world.small_key_shuffle[player] else 0x00) rom.write_byte(0x18016A, 0x10 | ((0x01 if local_world.options.small_key_shuffle else 0x00)
| (0x02 if world.compass_shuffle[player] else 0x00) | (0x02 if local_world.options.compass_shuffle else 0x00)
| (0x04 if world.map_shuffle[player] else 0x00) | (0x04 if local_world.options.map_shuffle else 0x00)
| (0x08 if world.big_key_shuffle[ | (0x08 if local_world.options.big_key_shuffle else 0x00))) # free roaming item text boxes
player] else 0x00))) # free roaming item text boxes rom.write_byte(0x18003B, 0x01 if local_world.options.map_shuffle else 0x00) # maps showing crystals on overworld
rom.write_byte(0x18003B, 0x01 if world.map_shuffle[player] else 0x00) # maps showing crystals on overworld
# compasses showing dungeon count # compasses showing dungeon count
if local_world.clock_mode or world.dungeon_counters[player] == 'off': if local_world.clock_mode or local_world.options.dungeon_counters == 'off':
rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location
elif world.dungeon_counters[player] == 'on': elif local_world.options.dungeon_counters == 'on':
rom.write_byte(0x18003C, 0x02) # always on rom.write_byte(0x18003C, 0x02) # always on
elif world.compass_shuffle[player] or world.dungeon_counters[player] == 'pickup': elif local_world.options.compass_shuffle or local_world.options.dungeon_counters == 'pickup':
rom.write_byte(0x18003C, 0x01) # show on pickup rom.write_byte(0x18003C, 0x01) # show on pickup
else: else:
rom.write_byte(0x18003C, 0x00) rom.write_byte(0x18003C, 0x00)
@@ -1574,11 +1568,11 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
# b - Big Key # b - Big Key
# a - Small Key # a - Small Key
# #
rom.write_byte(0x180045, ((0x00 if (world.small_key_shuffle[player] == small_key_shuffle.option_original_dungeon or rom.write_byte(0x180045, ((0x00 if (local_world.options.small_key_shuffle == small_key_shuffle.option_original_dungeon or
world.small_key_shuffle[player] == small_key_shuffle.option_universal) else 0x01) local_world.options.small_key_shuffle == small_key_shuffle.option_universal) else 0x01)
| (0x02 if world.big_key_shuffle[player] else 0x00) | (0x02 if local_world.options.big_key_shuffle else 0x00)
| (0x04 if world.map_shuffle[player] else 0x00) | (0x04 if local_world.options.map_shuffle else 0x00)
| (0x08 if world.compass_shuffle[player] else 0x00))) # free roaming items in menu | (0x08 if local_world.options.compass_shuffle else 0x00))) # free roaming items in menu
# Map reveals # Map reveals
reveal_bytes = { reveal_bytes = {
@@ -1604,31 +1598,25 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
return 0x0000 return 0x0000
rom.write_int16(0x18017A, rom.write_int16(0x18017A,
get_reveal_bytes('Green Pendant') if world.map_shuffle[player] else 0x0000) # Sahasrahla reveal get_reveal_bytes('Green Pendant') if local_world.options.map_shuffle else 0x0000) # Sahasrahla reveal
rom.write_int16(0x18017C, get_reveal_bytes('Crystal 5') | get_reveal_bytes('Crystal 6') if world.map_shuffle[ rom.write_int16(0x18017C, get_reveal_bytes('Crystal 5') | get_reveal_bytes('Crystal 6') if local_world.options.map_shuffle else 0x0000) # Bomb Shop Reveal
player] else 0x0000) # Bomb Shop Reveal
rom.write_byte(0x180172, 0x01 if world.small_key_shuffle[ rom.write_byte(0x180172, 0x01 if local_world.options.small_key_shuffle == small_key_shuffle.option_universal else 0x00) # universal keys
player] == small_key_shuffle.option_universal else 0x00) # universal keys rom.write_byte(0x18637E, 0x01 if local_world.options.retro_bow else 0x00) # Skip quiver in item shops once bought
rom.write_byte(0x18637E, 0x01 if world.retro_bow[player] else 0x00) # Skip quiver in item shops once bought rom.write_byte(0x180175, 0x01 if local_world.options.retro_bow else 0x00) # rupee bow
rom.write_byte(0x180175, 0x01 if world.retro_bow[player] else 0x00) # rupee bow rom.write_byte(0x180176, 0x0A if local_world.options.retro_bow else 0x00) # wood arrow cost
rom.write_byte(0x180176, 0x0A if world.retro_bow[player] else 0x00) # wood arrow cost rom.write_byte(0x180178, 0x32 if local_world.options.retro_bow else 0x00) # silver arrow cost
rom.write_byte(0x180178, 0x32 if world.retro_bow[player] else 0x00) # silver arrow cost rom.write_byte(0x301FC, 0xDA if local_world.options.retro_bow else 0xE1) # rupees replace arrows under pots
rom.write_byte(0x301FC, 0xDA if world.retro_bow[player] else 0xE1) # rupees replace arrows under pots rom.write_byte(0x30052, 0xDB if local_world.options.retro_bow else 0xE2) # replace arrows in fish prize from bottle merchant
rom.write_byte(0x30052, 0xDB if world.retro_bow[player] else 0xE2) # replace arrows in fish prize from bottle merchant rom.write_bytes(0xECB4E, [0xA9, 0x00, 0xEA, 0xEA] if local_world.options.retro_bow else [0xAF, 0x77, 0xF3, 0x7E]) # Thief steals rupees instead of arrows
rom.write_bytes(0xECB4E, [0xA9, 0x00, 0xEA, 0xEA] if world.retro_bow[player] else [0xAF, 0x77, 0xF3, rom.write_bytes(0xF0D96, [0xA9, 0x00, 0xEA, 0xEA] if local_world.options.retro_bow else [0xAF, 0x77, 0xF3, 0x7E]) # Pikit steals rupees instead of arrows
0x7E]) # Thief steals rupees instead of arrows rom.write_bytes(0xEDA5, [0x35, 0x41] if local_world.options.retro_bow else [0x43, 0x44]) # Chest game gives rupees instead of arrows
rom.write_bytes(0xF0D96, [0xA9, 0x00, 0xEA, 0xEA] if world.retro_bow[player] else [0xAF, 0x77, 0xF3,
0x7E]) # Pikit steals rupees instead of arrows
rom.write_bytes(0xEDA5,
[0x35, 0x41] if world.retro_bow[player] else [0x43, 0x44]) # Chest game gives rupees instead of arrows
digging_game_rng = local_random.randint(1, 30) # set rng for digging game digging_game_rng = local_random.randint(1, 30) # set rng for digging game
rom.write_byte(0x180020, digging_game_rng) rom.write_byte(0x180020, digging_game_rng)
rom.write_byte(0xEFD95, digging_game_rng) rom.write_byte(0xEFD95, digging_game_rng)
rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills
rom.write_byte(0x1800A4, 0x01 if world.glitches_required[player] != 'no_logic' else 0x00) # enable POD EG fix rom.write_byte(0x1800A4, 0x01 if local_world.options.glitches_required != 'no_logic' else 0x00) # enable POD EG fix
rom.write_byte(0x186383, 0x01 if world.glitches_required[ rom.write_byte(0x186383, 0x01 if local_world.options.glitches_required == 'no_logic' else 0x00) # disable glitching to Triforce from Ganons Room
player] == 'no_logic' else 0x00) # disable glitching to Triforce from Ganons Room
rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill
# remove shield from uncle # remove shield from uncle
@@ -1645,7 +1633,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_bytes(0x180185, [0, 0, 0]) # Uncle respawn refills (magic, bombs, arrows) rom.write_bytes(0x180185, [0, 0, 0]) # Uncle respawn refills (magic, bombs, arrows)
rom.write_bytes(0x180188, [0, 0, 0]) # Zelda respawn refills (magic, bombs, arrows) rom.write_bytes(0x180188, [0, 0, 0]) # Zelda respawn refills (magic, bombs, arrows)
rom.write_bytes(0x18018B, [0, 0, 0]) # Mantle respawn refills (magic, bombs, arrows) rom.write_bytes(0x18018B, [0, 0, 0]) # Mantle respawn refills (magic, bombs, arrows)
if world.mode[player] == 'standard' and uncle_location.item and uncle_location.item.player == player: if local_world.options.mode == 'standard' and uncle_location.item and uncle_location.item.player == player:
if uncle_location.item.name in {'Bow', 'Progressive Bow'}: if uncle_location.item.name in {'Bow', 'Progressive Bow'}:
rom.write_byte(0x18004E, 1) # Escape Fill (arrows) rom.write_byte(0x18004E, 1) # Escape Fill (arrows)
rom.write_int16(0x180183, 300) # Escape fill rupee bow rom.write_int16(0x180183, 300) # Escape fill rupee bow
@@ -1673,8 +1661,8 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
0xAD, 0xBF, 0x0A, 0xF0, 0x4F]) 0xAD, 0xBF, 0x0A, 0xF0, 0x4F])
# allow smith into multi-entrance caves in appropriate shuffles # allow smith into multi-entrance caves in appropriate shuffles
if world.entrance_shuffle[player] in ['restricted', 'full', 'crossed', 'insanity'] or ( if local_world.options.entrance_shuffle in ['restricted', 'full', 'crossed', 'insanity'] or (
world.entrance_shuffle[player] == 'simple' and world.mode[player] == 'inverted'): local_world.options.entrance_shuffle == 'simple' and local_world.options.mode == 'inverted'):
rom.write_byte(0x18004C, 0x01) rom.write_byte(0x18004C, 0x01)
# set correct flag for hera basement item # set correct flag for hera basement item
@@ -1694,8 +1682,8 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_byte(0xFED31, 0x2A) # bombable exit rom.write_byte(0xFED31, 0x2A) # bombable exit
rom.write_byte(0xFEE41, 0x2A) # bombable exit rom.write_byte(0xFEE41, 0x2A) # bombable exit
if world.tile_shuffle[player]: if local_world.options.tile_shuffle:
tile_set = TileSet.get_random_tile_set(world.per_slot_randoms[player]) tile_set = TileSet.get_random_tile_set(world.worlds[player].random)
rom.write_byte(0x4BA21, tile_set.get_speed()) rom.write_byte(0x4BA21, tile_set.get_speed())
rom.write_byte(0x4BA1D, tile_set.get_len()) rom.write_byte(0x4BA1D, tile_set.get_len())
rom.write_bytes(0x4BA2A, tile_set.get_bytes()) rom.write_bytes(0x4BA2A, tile_set.get_bytes())
@@ -1770,9 +1758,9 @@ def write_custom_shops(rom, world, player):
slot = 0 if shop.type == ShopType.TakeAny else index slot = 0 if shop.type == ShopType.TakeAny else index
if item is None: if item is None:
break break
if world.shop_item_slots[player] or shop.type == ShopType.TakeAny: if world.worlds[player].options.shop_item_slots or shop.type == ShopType.TakeAny:
count_shop = (shop.region.name != 'Potion Shop' or world.include_witch_hut[player]) and \ count_shop = (shop.region.name != 'Potion Shop' or world.worlds[player].options.include_witch_hut) and \
(shop.region.name != 'Capacity Upgrade' or world.shuffle_capacity_upgrades[player]) (shop.region.name != 'Capacity Upgrade' or world.worlds[player].options.shuffle_capacity_upgrades)
rom.write_byte(0x186560 + shop.sram_offset + slot, 1 if count_shop else 0) rom.write_byte(0x186560 + shop.sram_offset + slot, 1 if count_shop else 0)
if item['item'] == 'Single Arrow' and item['player'] == 0: if item['item'] == 'Single Arrow' and item['player'] == 0:
arrow_mask |= 1 << index arrow_mask |= 1 << index
@@ -1789,7 +1777,7 @@ def write_custom_shops(rom, world, player):
item_code = get_nonnative_item_sprite(world.worlds[item['player']].item_name_to_id[item['item']]) item_code = get_nonnative_item_sprite(world.worlds[item['player']].item_name_to_id[item['item']])
else: else:
item_code = item_table[item["item"]].item_code item_code = item_table[item["item"]].item_code
if item['item'] == 'Single Arrow' and item['player'] == 0 and world.retro_bow[player]: if item['item'] == 'Single Arrow' and item['player'] == 0 and world.worlds[player].options.retro_bow:
rom.write_byte(0x186500 + shop.sram_offset + slot, arrow_mask) rom.write_byte(0x186500 + shop.sram_offset + slot, arrow_mask)
item_data = [shop_id, item_code] + price_data + \ item_data = [shop_id, item_code] + price_data + \
@@ -1802,7 +1790,7 @@ def write_custom_shops(rom, world, player):
items_data.extend([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) items_data.extend([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
rom.write_bytes(0x184900, items_data) rom.write_bytes(0x184900, items_data)
if world.retro_bow[player]: if world.worlds[player].options.retro_bow:
retro_shop_slots.append(0xFF) retro_shop_slots.append(0xFF)
rom.write_bytes(0x186540, retro_shop_slots) rom.write_bytes(0x186540, retro_shop_slots)
@@ -2207,19 +2195,18 @@ def write_string_to_rom(rom, target, string):
def write_strings(rom, world, player): def write_strings(rom, world, player):
from . import ALTTPWorld from . import ALTTPWorld
local_random = world.worlds[player].random
w: ALTTPWorld = world.worlds[player] w: ALTTPWorld = world.worlds[player]
local_random = w.random
tt = TextTable() tt = TextTable()
tt.removeUnwantedText() tt.removeUnwantedText()
# Let's keep this guy's text accurate to the shuffle setting. # Let's keep this guy's text accurate to the shuffle setting.
if world.entrance_shuffle[player] in ['vanilla', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed']: if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed']:
tt['kakariko_flophouse_man_no_flippers'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.' tt['kakariko_flophouse_man_no_flippers'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.'
tt['kakariko_flophouse_man'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.' tt['kakariko_flophouse_man'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.'
if world.mode[player] == 'inverted': if world.worlds[player].options.mode == 'inverted':
tt['sign_village_of_outcasts'] = 'attention\nferal ducks sighted\nhiding in statues\n\nflute players beware\n' tt['sign_village_of_outcasts'] = 'attention\nferal ducks sighted\nhiding in statues\n\nflute players beware\n'
def hint_text(dest, ped_hint=False): def hint_text(dest, ped_hint=False):
@@ -2238,21 +2225,21 @@ def write_strings(rom, world, player):
hint += f" for {world.player_name[dest.player]}" hint += f" for {world.player_name[dest.player]}"
return hint return hint
if world.scams[player].gives_king_zora_hint: if world.worlds[player].options.scams.gives_king_zora_hint:
# Zora hint # Zora hint
zora_location = world.get_location("King Zora", player) zora_location = world.get_location("King Zora", player)
tt['zora_tells_cost'] = f"You got 500 rupees to buy {hint_text(zora_location.item)}" \ tt['zora_tells_cost'] = f"You got 500 rupees to buy {hint_text(zora_location.item)}" \
f"\n ≥ Duh\n Oh carp\n{{CHOICE}}" f"\n ≥ Duh\n Oh carp\n{{CHOICE}}"
if world.scams[player].gives_bottle_merchant_hint: if world.worlds[player].options.scams.gives_bottle_merchant_hint:
# Bottle Vendor hint # Bottle Vendor hint
vendor_location = world.get_location("Bottle Merchant", player) vendor_location = world.get_location("Bottle Merchant", player)
tt['bottle_vendor_choice'] = f"I gots {hint_text(vendor_location.item)}\nYous gots 100 rupees?" \ tt['bottle_vendor_choice'] = f"I gots {hint_text(vendor_location.item)}\nYous gots 100 rupees?" \
f"\n ≥ I want\n no way!\n{{CHOICE}}" f"\n ≥ I want\n no way!\n{{CHOICE}}"
# First we write hints about entrances, some from the inconvenient list others from all reasonable entrances. # First we write hints about entrances, some from the inconvenient list others from all reasonable entrances.
if world.hints[player]: if world.worlds[player].options.hints:
if world.hints[player].value >= 2: if world.worlds[player].options.hints.value >= 2:
if world.hints[player] == "full": if world.worlds[player].options.hints == "full":
tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles have hints!' tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles have hints!'
else: else:
tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles can have hints!' tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles can have hints!'
@@ -2265,11 +2252,11 @@ def write_strings(rom, world, player):
entrances_to_hint = {} entrances_to_hint = {}
entrances_to_hint.update(InconvenientDungeonEntrances) entrances_to_hint.update(InconvenientDungeonEntrances)
if world.shuffle_ganon: if world.shuffle_ganon:
if world.mode[player] == 'inverted': if world.worlds[player].options.mode == 'inverted':
entrances_to_hint.update({'Inverted Ganons Tower': 'The sealed castle door'}) entrances_to_hint.update({'Inverted Ganons Tower': 'The sealed castle door'})
else: else:
entrances_to_hint.update({'Ganons Tower': 'Ganon\'s Tower'}) entrances_to_hint.update({'Ganons Tower': 'Ganon\'s Tower'})
if world.entrance_shuffle[player] in ['simple', 'restricted']: if world.worlds[player].options.entrance_shuffle in ['simple', 'restricted']:
for entrance in all_entrances: for entrance in all_entrances:
if entrance.name in entrances_to_hint: if entrance.name in entrances_to_hint:
this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text( this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text(
@@ -2279,9 +2266,9 @@ def write_strings(rom, world, player):
break break
# Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones. # Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones.
entrances_to_hint.update(InconvenientOtherEntrances) entrances_to_hint.update(InconvenientOtherEntrances)
if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
hint_count = 0 hint_count = 0
elif world.entrance_shuffle[player] in ['simple', 'restricted']: elif world.worlds[player].options.entrance_shuffle in ['simple', 'restricted']:
hint_count = 2 hint_count = 2
else: else:
hint_count = 4 hint_count = 4
@@ -2298,31 +2285,31 @@ def write_strings(rom, world, player):
# Next we handle hints for randomly selected other entrances, # Next we handle hints for randomly selected other entrances,
# curating the selection intelligently based on shuffle. # curating the selection intelligently based on shuffle.
if world.entrance_shuffle[player] not in ['simple', 'restricted']: if world.worlds[player].options.entrance_shuffle not in ['simple', 'restricted']:
entrances_to_hint.update(ConnectorEntrances) entrances_to_hint.update(ConnectorEntrances)
entrances_to_hint.update(DungeonEntrances) entrances_to_hint.update(DungeonEntrances)
if world.mode[player] == 'inverted': if world.worlds[player].options.mode == 'inverted':
entrances_to_hint.update({'Inverted Agahnims Tower': 'The dark mountain tower'}) entrances_to_hint.update({'Inverted Agahnims Tower': 'The dark mountain tower'})
else: else:
entrances_to_hint.update({'Agahnims Tower': 'The sealed castle door'}) entrances_to_hint.update({'Agahnims Tower': 'The sealed castle door'})
elif world.entrance_shuffle[player] == 'restricted': elif world.worlds[player].options.entrance_shuffle == 'restricted':
entrances_to_hint.update(ConnectorEntrances) entrances_to_hint.update(ConnectorEntrances)
entrances_to_hint.update(OtherEntrances) entrances_to_hint.update(OtherEntrances)
if world.mode[player] == 'inverted': if world.worlds[player].options.mode == 'inverted':
entrances_to_hint.update({'Inverted Dark Sanctuary': 'The dark sanctuary cave'}) entrances_to_hint.update({'Inverted Dark Sanctuary': 'The dark sanctuary cave'})
entrances_to_hint.update({'Inverted Big Bomb Shop': 'The old hero\'s dark home'}) entrances_to_hint.update({'Inverted Big Bomb Shop': 'The old hero\'s dark home'})
entrances_to_hint.update({'Inverted Links House': 'The old hero\'s light home'}) entrances_to_hint.update({'Inverted Links House': 'The old hero\'s light home'})
else: else:
entrances_to_hint.update({'Dark Sanctuary Hint': 'The dark sanctuary cave'}) entrances_to_hint.update({'Dark Sanctuary Hint': 'The dark sanctuary cave'})
entrances_to_hint.update({'Big Bomb Shop': 'The old bomb shop'}) entrances_to_hint.update({'Big Bomb Shop': 'The old bomb shop'})
if world.entrance_shuffle[player] != 'insanity': if world.worlds[player].options.entrance_shuffle != 'insanity':
entrances_to_hint.update(InsanityEntrances) entrances_to_hint.update(InsanityEntrances)
if world.shuffle_ganon: if world.shuffle_ganon:
if world.mode[player] == 'inverted': if world.worlds[player].options.mode == 'inverted':
entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'}) entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'})
else: else:
entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'}) entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'})
hint_count = 4 if world.entrance_shuffle[player] not in ['vanilla', 'dungeons_simple', 'dungeons_full', hint_count = 4 if world.worlds[player].options.entrance_shuffle not in ['vanilla', 'dungeons_simple', 'dungeons_full',
'dungeons_crossed'] else 0 'dungeons_crossed'] else 0
for entrance in all_entrances: for entrance in all_entrances:
if entrance.name in entrances_to_hint: if entrance.name in entrances_to_hint:
@@ -2337,10 +2324,10 @@ def write_strings(rom, world, player):
# Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable. # Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable.
locations_to_hint = InconvenientLocations.copy() locations_to_hint = InconvenientLocations.copy()
if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
locations_to_hint.extend(InconvenientVanillaLocations) locations_to_hint.extend(InconvenientVanillaLocations)
local_random.shuffle(locations_to_hint) local_random.shuffle(locations_to_hint)
hint_count = 3 if world.entrance_shuffle[player] not in ['vanilla', 'dungeons_simple', 'dungeons_full', hint_count = 3 if world.worlds[player].options.entrance_shuffle not in ['vanilla', 'dungeons_simple', 'dungeons_full',
'dungeons_crossed'] else 5 'dungeons_crossed'] else 5
for location in locations_to_hint[:hint_count]: for location in locations_to_hint[:hint_count]:
if location == 'Swamp Left': if location == 'Swamp Left':
@@ -2395,15 +2382,15 @@ def write_strings(rom, world, player):
# Lastly we write hints to show where certain interesting items are. # Lastly we write hints to show where certain interesting items are.
items_to_hint = RelevantItems.copy() items_to_hint = RelevantItems.copy()
if world.small_key_shuffle[player].hints_useful: if world.worlds[player].options.small_key_shuffle.hints_useful:
items_to_hint |= item_name_groups["Small Keys"] items_to_hint |= item_name_groups["Small Keys"]
if world.big_key_shuffle[player].hints_useful: if world.worlds[player].options.big_key_shuffle.hints_useful:
items_to_hint |= item_name_groups["Big Keys"] items_to_hint |= item_name_groups["Big Keys"]
if world.hints[player] == "full": if world.worlds[player].options.hints == "full":
hint_count = len(hint_locations) # fill all remaining hint locations with Item hints. hint_count = len(hint_locations) # fill all remaining hint locations with Item hints.
else: else:
hint_count = 5 if world.entrance_shuffle[player] not in ['vanilla', 'dungeons_simple', 'dungeons_full', hint_count = 5 if world.worlds[player].options.entrance_shuffle not in ['vanilla', 'dungeons_simple', 'dungeons_full',
'dungeons_crossed'] else 8 'dungeons_crossed'] else 8
hint_count = min(hint_count, len(items_to_hint), len(hint_locations)) hint_count = min(hint_count, len(items_to_hint), len(hint_locations))
if hint_count: if hint_count:
@@ -2434,7 +2421,7 @@ def write_strings(rom, world, player):
tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint
tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint
if world.worlds[player].has_progressive_bows and (w.difficulty_requirements.progressive_bow_limit >= 2 or ( if world.worlds[player].has_progressive_bows and (w.difficulty_requirements.progressive_bow_limit >= 2 or (
world.swordless[player] or world.glitches_required[player] == 'no_glitches')): world.worlds[player].options.swordless or world.worlds[player].options.glitches_required == 'no_glitches')):
prog_bow_locs = world.find_item_locations('Progressive Bow', player, True) prog_bow_locs = world.find_item_locations('Progressive Bow', player, True)
local_random.shuffle(prog_bow_locs) local_random.shuffle(prog_bow_locs)
found_bow = False found_bow = False
@@ -2458,26 +2445,26 @@ def write_strings(rom, world, player):
greenpendant = world.find_item('Green Pendant', player) greenpendant = world.find_item('Green Pendant', player)
tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant.hint_text tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant.hint_text
if world.crystals_needed_for_gt[player] == 1: if world.worlds[player].options.crystals_needed_for_gt == 1:
tt['sign_ganons_tower'] = 'You need a crystal to enter.' tt['sign_ganons_tower'] = 'You need a crystal to enter.'
else: else:
tt['sign_ganons_tower'] = f'You need {world.crystals_needed_for_gt[player]} crystals to enter.' tt['sign_ganons_tower'] = f'You need {world.worlds[player].options.crystals_needed_for_gt} crystals to enter.'
if world.goal[player] == 'bosses': if world.worlds[player].options.goal == 'bosses':
tt['sign_ganon'] = 'You need to kill all bosses, Ganon last.' tt['sign_ganon'] = 'You need to kill all bosses, Ganon last.'
elif world.goal[player] == 'ganon_pedestal': elif world.worlds[player].options.goal == 'ganon_pedestal':
tt['sign_ganon'] = 'You need to pull the pedestal to defeat Ganon.' tt['sign_ganon'] = 'You need to pull the pedestal to defeat Ganon.'
elif world.goal[player] == "ganon": elif world.worlds[player].options.goal == "ganon":
if world.crystals_needed_for_ganon[player] == 1: if world.worlds[player].options.crystals_needed_for_ganon == 1:
tt['sign_ganon'] = 'You need a crystal to beat Ganon and have beaten Agahnim atop Ganons Tower.' tt['sign_ganon'] = 'You need a crystal to beat Ganon and have beaten Agahnim atop Ganons Tower.'
else: else:
tt['sign_ganon'] = f'You need {world.crystals_needed_for_ganon[player]} crystals to beat Ganon and ' \ tt['sign_ganon'] = f'You need {world.worlds[player].options.crystals_needed_for_ganon} crystals to beat Ganon and ' \
f'have beaten Agahnim atop Ganons Tower' f'have beaten Agahnim atop Ganons Tower'
else: else:
if world.crystals_needed_for_ganon[player] == 1: if world.worlds[player].options.crystals_needed_for_ganon == 1:
tt['sign_ganon'] = 'You need a crystal to beat Ganon.' tt['sign_ganon'] = 'You need a crystal to beat Ganon.'
else: else:
tt['sign_ganon'] = f'You need {world.crystals_needed_for_ganon[player]} crystals to beat Ganon.' tt['sign_ganon'] = f'You need {world.worlds[player].options.crystals_needed_for_ganon} crystals to beat Ganon.'
tt['uncle_leaving_text'] = Uncle_texts[local_random.randint(0, len(Uncle_texts) - 1)] tt['uncle_leaving_text'] = Uncle_texts[local_random.randint(0, len(Uncle_texts) - 1)]
tt['end_triforce'] = "{NOBORDER}\n" + Triforce_texts[local_random.randint(0, len(Triforce_texts) - 1)] tt['end_triforce'] = "{NOBORDER}\n" + Triforce_texts[local_random.randint(0, len(Triforce_texts) - 1)]
@@ -2490,10 +2477,10 @@ def write_strings(rom, world, player):
triforce_pieces_required = max(0, w.treasure_hunt_required - triforce_pieces_required = max(0, w.treasure_hunt_required -
sum(1 for item in world.precollected_items[player] if item.name == "Triforce Piece")) sum(1 for item in world.precollected_items[player] if item.name == "Triforce Piece"))
if world.goal[player] in ['triforce_hunt', 'local_triforce_hunt']: if world.worlds[player].options.goal in ['triforce_hunt', 'local_triforce_hunt']:
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.' tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.'
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
if world.goal[player] == 'triforce_hunt' and world.players > 1: if world.worlds[player].options.goal == 'triforce_hunt' and world.players > 1:
tt['sign_ganon'] = 'Go find the Triforce pieces with your friends... Ganon is invincible!' tt['sign_ganon'] = 'Go find the Triforce pieces with your friends... Ganon is invincible!'
else: else:
tt['sign_ganon'] = 'Go find the Triforce pieces... Ganon is invincible!' tt['sign_ganon'] = 'Go find the Triforce pieces... Ganon is invincible!'
@@ -2507,7 +2494,7 @@ def write_strings(rom, world, player):
"invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \ "invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \
"hidden in a hollow tree. If you bring\n%d Triforce piece out of %d, I can reassemble it." % \ "hidden in a hollow tree. If you bring\n%d Triforce piece out of %d, I can reassemble it." % \
(triforce_pieces_required, w.treasure_hunt_total) (triforce_pieces_required, w.treasure_hunt_total)
elif world.goal[player] in ['pedestal']: elif world.worlds[player].options.goal in ['pedestal']:
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Your goal is at the pedestal.' tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Your goal is at the pedestal.'
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
tt['sign_ganon'] = 'You need to get to the pedestal... Ganon is invincible!' tt['sign_ganon'] = 'You need to get to the pedestal... Ganon is invincible!'
@@ -2516,17 +2503,17 @@ def write_strings(rom, world, player):
tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!' tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!'
tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!' tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
if triforce_pieces_required > 1: if triforce_pieces_required > 1:
if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1: if world.worlds[player].options.goal == 'ganon_triforce_hunt' and world.players > 1:
tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d with your friends to defeat Ganon.' % \ tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d with your friends to defeat Ganon.' % \
(triforce_pieces_required, w.treasure_hunt_total) (triforce_pieces_required, w.treasure_hunt_total)
elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: elif world.worlds[player].options.goal in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d to defeat Ganon.' % \ tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d to defeat Ganon.' % \
(triforce_pieces_required, w.treasure_hunt_total) (triforce_pieces_required, w.treasure_hunt_total)
else: else:
if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1: if world.worlds[player].options.goal == 'ganon_triforce_hunt' and world.players > 1:
tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d with your friends to defeat Ganon.' % \ tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d with your friends to defeat Ganon.' % \
(triforce_pieces_required, w.treasure_hunt_total) (triforce_pieces_required, w.treasure_hunt_total)
elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: elif world.worlds[player].options.goal in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d to defeat Ganon.' % \ tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d to defeat Ganon.' % \
(triforce_pieces_required, w.treasure_hunt_total) (triforce_pieces_required, w.treasure_hunt_total)
@@ -2549,11 +2536,11 @@ def write_strings(rom, world, player):
tt['tablet_bombos_book'] = bombos_text tt['tablet_bombos_book'] = bombos_text
# inverted spawn menu changes # inverted spawn menu changes
if world.mode[player] == 'inverted': if world.worlds[player].options.mode == 'inverted':
tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n{CHOICE3}" tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n{CHOICE3}"
tt['menu_start_3'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n Mountain Cave\n{CHOICE2}" tt['menu_start_3'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n Mountain Cave\n{CHOICE2}"
for at, text, _ in world.plando_texts[player]: for at, text, _ in world.worlds[player].options.plando_texts:
if at not in tt: if at not in tt:
raise Exception(f"No text target \"{at}\" found.") raise Exception(f"No text target \"{at}\" found.")
@@ -2626,12 +2613,12 @@ def set_inverted_mode(world, player, rom):
rom.write_byte(snes_to_pc(0x08D40C), 0xD0) # morph proof rom.write_byte(snes_to_pc(0x08D40C), 0xD0) # morph proof
# the following bytes should only be written in vanilla # the following bytes should only be written in vanilla
# or they'll overwrite the randomizer's shuffles # or they'll overwrite the randomizer's shuffles
if world.entrance_shuffle[player] == 'vanilla': if world.worlds[player].options.entrance_shuffle == 'vanilla':
rom.write_byte(0xDBB73 + 0x23, 0x37) # switch AT and GT rom.write_byte(0xDBB73 + 0x23, 0x37) # switch AT and GT
rom.write_byte(0xDBB73 + 0x36, 0x24) rom.write_byte(0xDBB73 + 0x36, 0x24)
rom.write_int16(0x15AEE + 2 * 0x38, 0x00E0) rom.write_int16(0x15AEE + 2 * 0x38, 0x00E0)
rom.write_int16(0x15AEE + 2 * 0x25, 0x000C) rom.write_int16(0x15AEE + 2 * 0x25, 0x000C)
if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
rom.write_byte(0x15B8C, 0x6C) rom.write_byte(0x15B8C, 0x6C)
rom.write_byte(0xDBB73 + 0x00, 0x53) # switch bomb shop and links house rom.write_byte(0xDBB73 + 0x00, 0x53) # switch bomb shop and links house
rom.write_byte(0xDBB73 + 0x52, 0x01) rom.write_byte(0xDBB73 + 0x52, 0x01)
@@ -2689,7 +2676,7 @@ def set_inverted_mode(world, player, rom):
rom.write_int16(snes_to_pc(0x02D9A6), 0x005A) rom.write_int16(snes_to_pc(0x02D9A6), 0x005A)
rom.write_byte(snes_to_pc(0x02D9B3), 0x12) rom.write_byte(snes_to_pc(0x02D9B3), 0x12)
# keep the old man spawn point at old man house unless shuffle is vanilla # keep the old man spawn point at old man house unless shuffle is vanilla
if world.entrance_shuffle[player] in ['vanilla', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed']: if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed']:
rom.write_bytes(snes_to_pc(0x308350), [0x00, 0x00, 0x01]) rom.write_bytes(snes_to_pc(0x308350), [0x00, 0x00, 0x01])
rom.write_int16(snes_to_pc(0x02D8DE), 0x00F1) rom.write_int16(snes_to_pc(0x02D8DE), 0x00F1)
rom.write_bytes(snes_to_pc(0x02D910), [0x1F, 0x1E, 0x1F, 0x1F, 0x03, 0x02, 0x03, 0x03]) rom.write_bytes(snes_to_pc(0x02D910), [0x1F, 0x1E, 0x1F, 0x1F, 0x03, 0x02, 0x03, 0x03])
@@ -2752,7 +2739,7 @@ def set_inverted_mode(world, player, rom):
rom.write_int16s(snes_to_pc(0x1bb836), [0x001B, 0x001B, 0x001B]) rom.write_int16s(snes_to_pc(0x1bb836), [0x001B, 0x001B, 0x001B])
rom.write_int16(snes_to_pc(0x308300), 0x0140) # new pyramid hole entrance rom.write_int16(snes_to_pc(0x308300), 0x0140) # new pyramid hole entrance
rom.write_int16(snes_to_pc(0x308320), 0x001B) rom.write_int16(snes_to_pc(0x308320), 0x001B)
if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
rom.write_byte(snes_to_pc(0x308340), 0x7B) rom.write_byte(snes_to_pc(0x308340), 0x7B)
rom.write_int16(snes_to_pc(0x1af504), 0x148B) rom.write_int16(snes_to_pc(0x1af504), 0x148B)
rom.write_int16(snes_to_pc(0x1af50c), 0x149B) rom.write_int16(snes_to_pc(0x1af50c), 0x149B)
@@ -2789,10 +2776,10 @@ def set_inverted_mode(world, player, rom):
rom.write_bytes(snes_to_pc(0x1BC85A), [0x50, 0x0F, 0x82]) rom.write_bytes(snes_to_pc(0x1BC85A), [0x50, 0x0F, 0x82])
rom.write_int16(0xDB96F + 2 * 0x35, 0x001B) # move pyramid exit door rom.write_int16(0xDB96F + 2 * 0x35, 0x001B) # move pyramid exit door
rom.write_int16(0xDBA71 + 2 * 0x35, 0x06A4) rom.write_int16(0xDBA71 + 2 * 0x35, 0x06A4)
if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
rom.write_byte(0xDBB73 + 0x35, 0x36) rom.write_byte(0xDBB73 + 0x35, 0x36)
rom.write_byte(snes_to_pc(0x09D436), 0xF3) # remove castle gate warp rom.write_byte(snes_to_pc(0x09D436), 0xF3) # remove castle gate warp
if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
rom.write_int16(0x15AEE + 2 * 0x37, 0x0010) # pyramid exit to new hc area rom.write_int16(0x15AEE + 2 * 0x37, 0x0010) # pyramid exit to new hc area
rom.write_byte(0x15B8C + 0x37, 0x1B) rom.write_byte(0x15B8C + 0x37, 0x1B)
rom.write_int16(0x15BDB + 2 * 0x37, 0x0418) rom.write_int16(0x15BDB + 2 * 0x37, 0x0418)

View File

@@ -27,9 +27,9 @@ from .UnderworldGlitchRules import underworld_glitches_rules
def set_rules(world): def set_rules(world):
player = world.player player = world.player
world = world.multiworld world = world.multiworld
if world.glitches_required[player] == 'no_logic': if world.worlds[player].options.glitches_required == 'no_logic':
if player == next(player_id for player_id in world.get_game_players("A Link to the Past") if player == next(player_id for player_id in world.get_game_players("A Link to the Past")
if world.glitches_required[player_id] == 'no_logic'): # only warn one time if world.worlds[player_id].options.glitches_required == 'no_logic'): # only warn one time
logging.info( logging.info(
'WARNING! Seeds generated under this logic often require major glitches and may be impossible!') 'WARNING! Seeds generated under this logic often require major glitches and may be impossible!')
@@ -40,8 +40,8 @@ def set_rules(world):
else: else:
# Set access rules according to max glitches for multiworld progression. # Set access rules according to max glitches for multiworld progression.
# Set accessibility to none, and shuffle assuming the no logic players can always win # Set accessibility to none, and shuffle assuming the no logic players can always win
world.accessibility[player].value = ItemsAccessibility.option_minimal world.worlds[player].options.accessibility.value = ItemsAccessibility.option_minimal
world.progression_balancing[player].value = 0 world.worlds[player].options.progression_balancing.value = 0
else: else:
world.completion_condition[player] = lambda state: state.has('Triforce', player) world.completion_condition[player] = lambda state: state.has('Triforce', player)
@@ -49,52 +49,52 @@ def set_rules(world):
dungeon_boss_rules(world, player) dungeon_boss_rules(world, player)
global_rules(world, player) global_rules(world, player)
if world.mode[player] != 'inverted': if world.worlds[player].options.mode != 'inverted':
default_rules(world, player) default_rules(world, player)
if world.mode[player] == 'open': if world.worlds[player].options.mode == 'open':
open_rules(world, player) open_rules(world, player)
elif world.mode[player] == 'standard': elif world.worlds[player].options.mode == 'standard':
standard_rules(world, player) standard_rules(world, player)
elif world.mode[player] == 'inverted': elif world.worlds[player].options.mode == 'inverted':
open_rules(world, player) open_rules(world, player)
inverted_rules(world, player) inverted_rules(world, player)
else: else:
raise NotImplementedError(f'World state {world.mode[player]} is not implemented yet') raise NotImplementedError(f'World state {world.worlds[player].options.mode} is not implemented yet')
if world.glitches_required[player] == 'no_glitches': if world.worlds[player].options.glitches_required == 'no_glitches':
no_glitches_rules(world, player) no_glitches_rules(world, player)
forbid_bomb_jump_requirements(world, player) forbid_bomb_jump_requirements(world, player)
elif world.glitches_required[player] == 'overworld_glitches': elif world.worlds[player].options.glitches_required == 'overworld_glitches':
# Initially setting no_glitches_rules to set the baseline rules for some # Initially setting no_glitches_rules to set the baseline rules for some
# entrances. The overworld_glitches_rules set is primarily additive. # entrances. The overworld_glitches_rules set is primarily additive.
no_glitches_rules(world, player) no_glitches_rules(world, player)
fake_flipper_rules(world, player) fake_flipper_rules(world, player)
overworld_glitches_rules(world, player) overworld_glitches_rules(world, player)
forbid_bomb_jump_requirements(world, player) forbid_bomb_jump_requirements(world, player)
elif world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']: elif world.worlds[player].options.glitches_required in ['hybrid_major_glitches', 'no_logic']:
no_glitches_rules(world, player) no_glitches_rules(world, player)
fake_flipper_rules(world, player) fake_flipper_rules(world, player)
overworld_glitches_rules(world, player) overworld_glitches_rules(world, player)
underworld_glitches_rules(world, player) underworld_glitches_rules(world, player)
bomb_jump_requirements(world, player) bomb_jump_requirements(world, player)
elif world.glitches_required[player] == 'minor_glitches': elif world.worlds[player].options.glitches_required == 'minor_glitches':
no_glitches_rules(world, player) no_glitches_rules(world, player)
fake_flipper_rules(world, player) fake_flipper_rules(world, player)
forbid_bomb_jump_requirements(world, player) forbid_bomb_jump_requirements(world, player)
else: else:
raise NotImplementedError(f'Not implemented yet: Logic - {world.glitches_required[player]}') raise NotImplementedError(f'Not implemented yet: Logic - {world.worlds[player].options.glitches_required}')
if world.goal[player] == 'bosses': if world.worlds[player].options.goal == 'bosses':
# require all bosses to beat ganon # require all bosses to beat ganon
add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player) and state.has('Beat Agahnim 1', player) and state.has('Beat Agahnim 2', player) and has_crystals(state, 7, player)) add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player) and state.has('Beat Agahnim 1', player) and state.has('Beat Agahnim 2', player) and has_crystals(state, 7, player))
elif world.goal[player] == 'ganon': elif world.worlds[player].options.goal == 'ganon':
# require aga2 to beat ganon # require aga2 to beat ganon
add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player)) add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player))
if world.mode[player] != 'inverted': if world.worlds[player].options.mode != 'inverted':
set_big_bomb_rules(world, player) set_big_bomb_rules(world, player)
if world.glitches_required[player].current_key in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.entrance_shuffle[player].current_key not in {'insanity', 'insanity_legacy', 'madness'}: if world.worlds[player].options.glitches_required.current_key in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.worlds[player].options.entrance_shuffle.current_key not in {'insanity', 'insanity_legacy', 'madness'}:
path_to_courtyard = mirrorless_path_to_castle_courtyard(world, player) path_to_courtyard = mirrorless_path_to_castle_courtyard(world, player)
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.multiworld.get_entrance('Dark Death Mountain Offset Mirror', player).can_reach(state) and all(rule(state) for rule in path_to_courtyard), 'or') add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.multiworld.get_entrance('Dark Death Mountain Offset Mirror', player).can_reach(state) and all(rule(state) for rule in path_to_courtyard), 'or')
else: else:
@@ -102,21 +102,24 @@ def set_rules(world):
# if swamp and dam have not been moved we require mirror for swamp palace # if swamp and dam have not been moved we require mirror for swamp palace
# however there is mirrorless swamp in hybrid MG, so we don't necessarily want this. HMG handles this requirement itself. # however there is mirrorless swamp in hybrid MG, so we don't necessarily want this. HMG handles this requirement itself.
if not world.worlds[player].swamp_patch_required and world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']: if not world.worlds[player].swamp_patch_required and world.worlds[player].options.glitches_required not in ['hybrid_major_glitches', 'no_logic']:
add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player)) add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player))
# GT Entrance may be required for Turtle Rock for OWG and < 7 required # GT Entrance may be required for Turtle Rock for OWG and < 7 required
ganons_tower = world.get_entrance('Inverted Ganons Tower' if world.mode[player] == 'inverted' else 'Ganons Tower', player) ganons_tower = world.get_entrance('Inverted Ganons Tower' if world.worlds[player].options.mode == 'inverted' else 'Ganons Tower', player)
if world.crystals_needed_for_gt[player] == 7 and not (world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and world.mode[player] != 'inverted'): if (world.worlds[player].options.crystals_needed_for_gt == 7
and not (world.worlds[player].options.glitches_required
in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']
and world.worlds[player].options.mode != 'inverted')):
set_rule(ganons_tower, lambda state: False) set_rule(ganons_tower, lambda state: False)
set_trock_key_rules(world, player) set_trock_key_rules(world, player)
set_rule(ganons_tower, lambda state: has_crystals(state, state.multiworld.crystals_needed_for_gt[player], player)) set_rule(ganons_tower, lambda state: has_crystals(state, state.multiworld.worlds[player].options.crystals_needed_for_gt, player))
if world.mode[player] != 'inverted' and world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: if world.worlds[player].options.mode != 'inverted' and world.worlds[player].options.glitches_required in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']:
add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.multiworld.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or') add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.multiworld.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or')
set_bunny_rules(world, player, world.mode[player] == 'inverted') set_bunny_rules(world, player, world.worlds[player].options.mode == 'inverted')
def mirrorless_path_to_castle_courtyard(world, player): def mirrorless_path_to_castle_courtyard(world, player):
@@ -150,17 +153,17 @@ def set_always_allow(spot, rule):
def add_lamp_requirement(world: MultiWorld, spot, player: int, has_accessible_torch: bool = False): def add_lamp_requirement(world: MultiWorld, spot, player: int, has_accessible_torch: bool = False):
if world.dark_room_logic[player] == "lamp": if world.worlds[player].options.dark_room_logic == "lamp":
add_rule(spot, lambda state: state.has('Lamp', player)) add_rule(spot, lambda state: state.has('Lamp', player))
elif world.dark_room_logic[player] == "torches": # implicitly lamp as well elif world.worlds[player].options.dark_room_logic == "torches": # implicitly lamp as well
if has_accessible_torch: if has_accessible_torch:
add_rule(spot, lambda state: state.has('Lamp', player) or state.has('Fire Rod', player)) add_rule(spot, lambda state: state.has('Lamp', player) or state.has('Fire Rod', player))
else: else:
add_rule(spot, lambda state: state.has('Lamp', player)) add_rule(spot, lambda state: state.has('Lamp', player))
elif world.dark_room_logic[player] == "none": elif world.worlds[player].options.dark_room_logic == "none":
pass pass
else: else:
raise ValueError(f"Unknown Dark Room Logic: {world.dark_room_logic[player]}") raise ValueError(f"Unknown Dark Room Logic: {world.worlds[player].options.dark_room_logic}")
non_crossover_items = (item_name_groups["Small Keys"] | item_name_groups["Big Keys"] | progression_items) - { non_crossover_items = (item_name_groups["Small Keys"] | item_name_groups["Big Keys"] | progression_items) - {
@@ -227,12 +230,13 @@ def global_rules(multiworld: MultiWorld, player: int):
set_rule(multiworld.get_location('Sick Kid', player), lambda state: state.has_group("Bottles", player)) set_rule(multiworld.get_location('Sick Kid', player), lambda state: state.has_group("Bottles", player))
set_rule(multiworld.get_location('Library', player), lambda state: state.has('Pegasus Boots', player)) set_rule(multiworld.get_location('Library', player), lambda state: state.has('Pegasus Boots', player))
if multiworld.enemy_shuffle[player]: if world.options.enemy_shuffle:
set_rule(multiworld.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player) and set_rule(multiworld.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player) and
can_kill_most_things(state, player, 4)) can_kill_most_things(state, player, 4))
else: else:
set_rule(multiworld.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player) set_rule(multiworld.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player)
and ((state.multiworld.enemy_health[player] in ("easy", "default") and can_use_bombs(state, player, 4)) and ((state.multiworld.worlds[player].options.enemy_health in ("easy", "default")
and can_use_bombs(state, player, 4))
or can_shoot_arrows(state, player) or state.has("Cane of Somaria", player) or can_shoot_arrows(state, player) or state.has("Cane of Somaria", player)
or has_beam_sword(state, player))) or has_beam_sword(state, player)))
@@ -299,8 +303,7 @@ def global_rules(multiworld: MultiWorld, player: int):
set_rule(multiworld.get_entrance('Sewers Door', player), set_rule(multiworld.get_entrance('Sewers Door', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) or ( lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) or (
multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal and multiworld.mode[ world.options.small_key_shuffle == small_key_shuffle.option_universal and world.options.mode == 'standard')) # standard universal small keys cannot access the shop
player] == 'standard')) # standard universal small keys cannot access the shop
set_rule(multiworld.get_entrance('Sewers Back Door', player), set_rule(multiworld.get_entrance('Sewers Back Door', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4)) lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4))
set_rule(multiworld.get_entrance('Sewers Secret Room', player), lambda state: can_bomb_or_bonk(state, player)) set_rule(multiworld.get_entrance('Sewers Secret Room', player), lambda state: can_bomb_or_bonk(state, player))
@@ -339,12 +342,12 @@ def global_rules(multiworld: MultiWorld, player: int):
add_rule(ep_prize, lambda state: state.has('Big Key (Eastern Palace)', player) and add_rule(ep_prize, lambda state: state.has('Big Key (Eastern Palace)', player) and
state._lttp_has_key('Small Key (Eastern Palace)', player, 2) and state._lttp_has_key('Small Key (Eastern Palace)', player, 2) and
ep_prize.parent_region.dungeon.boss.can_defeat(state)) ep_prize.parent_region.dungeon.boss.can_defeat(state))
if not multiworld.enemy_shuffle[player]: if not world.options.enemy_shuffle:
add_rule(ep_boss, lambda state: can_shoot_arrows(state, player)) add_rule(ep_boss, lambda state: can_shoot_arrows(state, player))
add_rule(ep_prize, lambda state: can_shoot_arrows(state, player)) add_rule(ep_prize, lambda state: can_shoot_arrows(state, player))
# You can always kill the Stalfos' with the pots on easy/normal # You can always kill the Stalfos' with the pots on easy/normal
if multiworld.enemy_health[player] in ("hard", "expert") or multiworld.enemy_shuffle[player]: if world.options.enemy_health in ("hard", "expert") or world.options.enemy_shuffle:
stalfos_rule = lambda state: can_kill_most_things(state, player, 4) stalfos_rule = lambda state: can_kill_most_things(state, player, 4)
for location in ['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest', for location in ['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest',
'Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop', 'Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop',
@@ -362,14 +365,14 @@ def global_rules(multiworld: MultiWorld, player: int):
add_rule(multiworld.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state)) add_rule(multiworld.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state))
# logic patch to prevent placing a crystal in Desert that's required to reach the required keys # logic patch to prevent placing a crystal in Desert that's required to reach the required keys
if not (multiworld.small_key_shuffle[player] and multiworld.big_key_shuffle[player]): if not (world.options.small_key_shuffle and world.options.big_key_shuffle):
add_rule(multiworld.get_location('Desert Palace - Prize', player), lambda state: state.multiworld.get_region('Desert Palace Main (Outer)', player).can_reach(state)) add_rule(multiworld.get_location('Desert Palace - Prize', player), lambda state: state.multiworld.get_region('Desert Palace Main (Outer)', player).can_reach(state))
set_rule(multiworld.get_location('Tower of Hera - Basement Cage', player), lambda state: can_activate_crystal_switch(state, player)) set_rule(multiworld.get_location('Tower of Hera - Basement Cage', player), lambda state: can_activate_crystal_switch(state, player))
set_rule(multiworld.get_location('Tower of Hera - Map Chest', player), lambda state: can_activate_crystal_switch(state, player)) set_rule(multiworld.get_location('Tower of Hera - Map Chest', player), lambda state: can_activate_crystal_switch(state, player))
set_rule(multiworld.get_entrance('Tower of Hera Small Key Door', player), lambda state: can_activate_crystal_switch(state, player) and (state._lttp_has_key('Small Key (Tower of Hera)', player) or location_item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player))) set_rule(multiworld.get_entrance('Tower of Hera Small Key Door', player), lambda state: can_activate_crystal_switch(state, player) and (state._lttp_has_key('Small Key (Tower of Hera)', player) or location_item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player)))
set_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player), lambda state: can_activate_crystal_switch(state, player) and state.has('Big Key (Tower of Hera)', player)) set_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player), lambda state: can_activate_crystal_switch(state, player) and state.has('Big Key (Tower of Hera)', player))
if multiworld.enemy_shuffle[player]: if world.options.enemy_shuffle:
add_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player), lambda state: can_kill_most_things(state, player, 3)) add_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player), lambda state: can_kill_most_things(state, player, 3))
else: else:
add_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player), add_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player),
@@ -378,7 +381,7 @@ def global_rules(multiworld: MultiWorld, player: int):
or state.has("Cane of Somaria", player))) or state.has("Cane of Somaria", player)))
set_rule(multiworld.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player)) set_rule(multiworld.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player))
set_rule(multiworld.get_location('Tower of Hera - Big Key Chest', player), lambda state: has_fire_source(state, player)) set_rule(multiworld.get_location('Tower of Hera - Big Key Chest', player), lambda state: has_fire_source(state, player))
if multiworld.accessibility[player] != 'full': if world.options.accessibility != 'full':
set_always_allow(multiworld.get_location('Tower of Hera - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Tower of Hera)' and item.player == player) set_always_allow(multiworld.get_location('Tower of Hera - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Tower of Hera)' and item.player == player)
set_rule(multiworld.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player)) set_rule(multiworld.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player))
@@ -387,32 +390,32 @@ def global_rules(multiworld: MultiWorld, player: int):
set_rule(multiworld.get_location('Swamp Palace - Trench 1 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 2)) set_rule(multiworld.get_location('Swamp Palace - Trench 1 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 2))
set_rule(multiworld.get_entrance('Swamp Palace (Center)', player), lambda state: state.has('Hammer', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 3)) set_rule(multiworld.get_entrance('Swamp Palace (Center)', player), lambda state: state.has('Hammer', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 3))
set_rule(multiworld.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: state.has('Hookshot', player)) set_rule(multiworld.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: state.has('Hookshot', player))
if multiworld.pot_shuffle[player]: if world.options.pot_shuffle:
# it could move the key to the top right platform which can only be reached with bombs # it could move the key to the top right platform which can only be reached with bombs
add_rule(multiworld.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: can_use_bombs(state, player)) add_rule(multiworld.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Swamp Palace (West)', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6) set_rule(multiworld.get_entrance('Swamp Palace (West)', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6)
if state.has('Hookshot', player) if state.has('Hookshot', player)
else state._lttp_has_key('Small Key (Swamp Palace)', player, 4)) else state._lttp_has_key('Small Key (Swamp Palace)', player, 4))
set_rule(multiworld.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player)) set_rule(multiworld.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player))
if multiworld.accessibility[player] != 'full': if world.options.accessibility != 'full':
allow_self_locking_items(multiworld.get_location('Swamp Palace - Big Chest', player), 'Big Key (Swamp Palace)') allow_self_locking_items(multiworld.get_location('Swamp Palace - Big Chest', player), 'Big Key (Swamp Palace)')
set_rule(multiworld.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 5)) set_rule(multiworld.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 5))
if not multiworld.small_key_shuffle[player] and multiworld.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']: if not world.options.small_key_shuffle and world.options.glitches_required not in ['hybrid_major_glitches', 'no_logic']:
forbid_item(multiworld.get_location('Swamp Palace - Entrance', player), 'Big Key (Swamp Palace)', player) forbid_item(multiworld.get_location('Swamp Palace - Entrance', player), 'Big Key (Swamp Palace)', player)
add_rule(multiworld.get_location('Swamp Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6)) add_rule(multiworld.get_location('Swamp Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6))
add_rule(multiworld.get_location('Swamp Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6)) add_rule(multiworld.get_location('Swamp Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6))
if multiworld.pot_shuffle[player]: if world.options.pot_shuffle:
# key can (and probably will) be moved behind bombable wall # key can (and probably will) be moved behind bombable wall
set_rule(multiworld.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: can_use_bombs(state, player)) set_rule(multiworld.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Thieves Town Big Key Door', player), lambda state: state.has('Big Key (Thieves Town)', player)) set_rule(multiworld.get_entrance('Thieves Town Big Key Door', player), lambda state: state.has('Big Key (Thieves Town)', player))
if multiworld.worlds[player].dungeons["Thieves Town"].boss.enemizer_name == "Blind": if world.dungeons["Thieves Town"].boss.enemizer_name == "Blind":
set_rule(multiworld.get_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3) and can_use_bombs(state, player)) set_rule(multiworld.get_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3) and can_use_bombs(state, player))
set_rule(multiworld.get_location('Thieves\' Town - Big Chest', player), set_rule(multiworld.get_location('Thieves\' Town - Big Chest', player),
lambda state: ((state._lttp_has_key('Small Key (Thieves Town)', player, 3)) or (location_item_name(state, 'Thieves\' Town - Big Chest', player) == ("Small Key (Thieves Town)", player)) and state._lttp_has_key('Small Key (Thieves Town)', player, 2)) and state.has('Hammer', player)) lambda state: ((state._lttp_has_key('Small Key (Thieves Town)', player, 3)) or (location_item_name(state, 'Thieves\' Town - Big Chest', player) == ("Small Key (Thieves Town)", player)) and state._lttp_has_key('Small Key (Thieves Town)', player, 2)) and state.has('Hammer', player))
set_rule(multiworld.get_location('Thieves\' Town - Blind\'s Cell', player), set_rule(multiworld.get_location('Thieves\' Town - Blind\'s Cell', player),
lambda state: state._lttp_has_key('Small Key (Thieves Town)', player)) lambda state: state._lttp_has_key('Small Key (Thieves Town)', player))
if multiworld.accessibility[player] != 'full' and not multiworld.key_drop_shuffle[player]: if world.options.accessibility != 'full' and not world.options.key_drop_shuffle:
set_always_allow(multiworld.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player) set_always_allow(multiworld.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player)
set_rule(multiworld.get_location('Thieves\' Town - Attic', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3)) set_rule(multiworld.get_location('Thieves\' Town - Attic', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3))
set_rule(multiworld.get_location('Thieves\' Town - Spike Switch Pot Key', player), set_rule(multiworld.get_location('Thieves\' Town - Spike Switch Pot Key', player),
@@ -424,7 +427,7 @@ def global_rules(multiworld: MultiWorld, player: int):
set_rule(multiworld.get_entrance('Skull Woods First Section West Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) set_rule(multiworld.get_entrance('Skull Woods First Section West Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
set_rule(multiworld.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) set_rule(multiworld.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
set_rule(multiworld.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) and can_use_bombs(state, player)) set_rule(multiworld.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) and can_use_bombs(state, player))
if multiworld.accessibility[player] != 'full': if world.options.accessibility != 'full':
allow_self_locking_items(multiworld.get_location('Skull Woods - Big Chest', player), 'Big Key (Skull Woods)') allow_self_locking_items(multiworld.get_location('Skull Woods - Big Chest', player), 'Big Key (Skull Woods)')
set_rule(multiworld.get_entrance('Skull Woods Torch Room', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 4) and state.has('Fire Rod', player) and has_sword(state, player)) # sword required for curtain set_rule(multiworld.get_entrance('Skull Woods Torch Room', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 4) and state.has('Fire Rod', player) and has_sword(state, player)) # sword required for curtain
add_rule(multiworld.get_location('Skull Woods - Prize', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) add_rule(multiworld.get_location('Skull Woods - Prize', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
@@ -501,13 +504,13 @@ def global_rules(multiworld: MultiWorld, player: int):
set_rule(multiworld.get_entrance('Turtle Rock (Trinexx)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6) and state.has('Big Key (Turtle Rock)', player) and state.has('Cane of Somaria', player)) set_rule(multiworld.get_entrance('Turtle Rock (Trinexx)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6) and state.has('Big Key (Turtle Rock)', player) and state.has('Cane of Somaria', player))
set_rule(multiworld.get_entrance('Turtle Rock Second Section Bomb Wall', player), lambda state: can_kill_most_things(state, player, 10)) set_rule(multiworld.get_entrance('Turtle Rock Second Section Bomb Wall', player), lambda state: can_kill_most_things(state, player, 10))
if not multiworld.worlds[player].fix_trock_doors: if not world.fix_trock_doors:
add_rule(multiworld.get_entrance('Turtle Rock Second Section Bomb Wall', player), lambda state: can_use_bombs(state, player)) add_rule(multiworld.get_entrance('Turtle Rock Second Section Bomb Wall', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Turtle Rock Second Section from Bomb Wall', player), lambda state: can_use_bombs(state, player)) set_rule(multiworld.get_entrance('Turtle Rock Second Section from Bomb Wall', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Turtle Rock Eye Bridge from Bomb Wall', player), lambda state: can_use_bombs(state, player)) set_rule(multiworld.get_entrance('Turtle Rock Eye Bridge from Bomb Wall', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Turtle Rock Eye Bridge Bomb Wall', player), lambda state: can_use_bombs(state, player)) set_rule(multiworld.get_entrance('Turtle Rock Eye Bridge Bomb Wall', player), lambda state: can_use_bombs(state, player))
if multiworld.enemy_shuffle[player]: if world.options.enemy_shuffle:
set_rule(multiworld.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_kill_most_things(state, player, 3)) set_rule(multiworld.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_kill_most_things(state, player, 3))
else: else:
set_rule(multiworld.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_shoot_arrows(state, player)) set_rule(multiworld.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_shoot_arrows(state, player))
@@ -517,18 +520,18 @@ def global_rules(multiworld: MultiWorld, player: int):
set_rule(multiworld.get_entrance('Palace of Darkness (North)', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 4)) set_rule(multiworld.get_entrance('Palace of Darkness (North)', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 4))
set_rule(multiworld.get_location('Palace of Darkness - Big Chest', player), lambda state: can_use_bombs(state, player) and state.has('Big Key (Palace of Darkness)', player)) set_rule(multiworld.get_location('Palace of Darkness - Big Chest', player), lambda state: can_use_bombs(state, player) and state.has('Big Key (Palace of Darkness)', player))
set_rule(multiworld.get_location('Palace of Darkness - The Arena - Ledge', player), lambda state: can_use_bombs(state, player)) set_rule(multiworld.get_location('Palace of Darkness - The Arena - Ledge', player), lambda state: can_use_bombs(state, player))
if multiworld.pot_shuffle[player]: if world.options.pot_shuffle:
# chest switch may be up on ledge where bombs are required # chest switch may be up on ledge where bombs are required
set_rule(multiworld.get_location('Palace of Darkness - Stalfos Basement', player), lambda state: can_use_bombs(state, player)) set_rule(multiworld.get_location('Palace of Darkness - Stalfos Basement', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: can_use_bombs(state, player) and (state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or ( set_rule(multiworld.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: can_use_bombs(state, player) and (state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or (
location_item_name(state, 'Palace of Darkness - Big Key Chest', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 3)))) location_item_name(state, 'Palace of Darkness - Big Key Chest', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 3))))
if multiworld.accessibility[player] != 'full': if world.options.accessibility != 'full':
set_always_allow(multiworld.get_location('Palace of Darkness - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5)) set_always_allow(multiworld.get_location('Palace of Darkness - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5))
set_rule(multiworld.get_entrance('Palace of Darkness Spike Statue Room Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or ( set_rule(multiworld.get_entrance('Palace of Darkness Spike Statue Room Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or (
location_item_name(state, 'Palace of Darkness - Harmless Hellway', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 4))) location_item_name(state, 'Palace of Darkness - Harmless Hellway', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 4)))
if multiworld.accessibility[player] != 'full': if world.options.accessibility != 'full':
set_always_allow(multiworld.get_location('Palace of Darkness - Harmless Hellway', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5)) set_always_allow(multiworld.get_location('Palace of Darkness - Harmless Hellway', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5))
set_rule(multiworld.get_entrance('Palace of Darkness Maze Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6)) set_rule(multiworld.get_entrance('Palace of Darkness Maze Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6))
@@ -541,13 +544,13 @@ def global_rules(multiworld: MultiWorld, player: int):
set_rule(multiworld.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has('Pegasus Boots', player)) set_rule(multiworld.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has('Pegasus Boots', player))
set_rule(multiworld.get_entrance('Ganons Tower (Tile Room)', player), lambda state: state.has('Cane of Somaria', player)) set_rule(multiworld.get_entrance('Ganons Tower (Tile Room)', player), lambda state: state.has('Cane of Somaria', player))
set_rule(multiworld.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player))) set_rule(multiworld.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player)))
if multiworld.pot_shuffle[player]: if world.options.pot_shuffle:
set_rule(multiworld.get_location('Ganons Tower - Conveyor Cross Pot Key', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player))) set_rule(multiworld.get_location('Ganons Tower - Conveyor Cross Pot Key', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player)))
set_rule(multiworld.get_entrance('Ganons Tower (Map Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or ( set_rule(multiworld.get_entrance('Ganons Tower (Map Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or (
location_item_name(state, 'Ganons Tower - Map Chest', player) in [('Big Key (Ganons Tower)', player)] and state._lttp_has_key('Small Key (Ganons Tower)', player, 6))) location_item_name(state, 'Ganons Tower - Map Chest', player) in [('Big Key (Ganons Tower)', player)] and state._lttp_has_key('Small Key (Ganons Tower)', player, 6)))
# this seemed to be causing generation failure, disable for now # this seemed to be causing generation failure, disable for now
# if world.accessibility[player] != 'full': # if world.worlds[player].options.accessibility != 'full':
# set_always_allow(world.get_location('Ganons Tower - Map Chest', player), lambda state, item: item.name == 'Small Key (Ganons Tower)' and item.player == player and state._lttp_has_key('Small Key (Ganons Tower)', player, 7) and state.can_reach('Ganons Tower (Hookshot Room)', 'region', player)) # set_always_allow(world.get_location('Ganons Tower - Map Chest', player), lambda state, item: item.name == 'Small Key (Ganons Tower)' and item.player == player and state._lttp_has_key('Small Key (Ganons Tower)', player, 7) and state.can_reach('Ganons Tower (Hookshot Room)', 'region', player))
# It is possible to need more than 6 keys to get through this entrance if you spend keys elsewhere. We reflect this in the chest requirements. # It is possible to need more than 6 keys to get through this entrance if you spend keys elsewhere. We reflect this in the chest requirements.
@@ -582,7 +585,7 @@ def global_rules(multiworld: MultiWorld, player: int):
lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
set_rule(multiworld.get_location('Ganons Tower - Big Key Room - Right', player), set_rule(multiworld.get_location('Ganons Tower - Big Key Room - Right', player),
lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
if multiworld.enemy_shuffle[player]: if world.options.enemy_shuffle:
set_rule(multiworld.get_entrance('Ganons Tower Big Key Door', player), set_rule(multiworld.get_entrance('Ganons Tower Big Key Door', player),
lambda state: state.has('Big Key (Ganons Tower)', player)) lambda state: state.has('Big Key (Ganons Tower)', player))
else: else:
@@ -600,12 +603,12 @@ def global_rules(multiworld: MultiWorld, player: int):
set_defeat_dungeon_boss_rule(multiworld.get_location('Agahnim 2', player)) set_defeat_dungeon_boss_rule(multiworld.get_location('Agahnim 2', player))
ganon = multiworld.get_location('Ganon', player) ganon = multiworld.get_location('Ganon', player)
set_rule(ganon, lambda state: GanonDefeatRule(state, player)) set_rule(ganon, lambda state: GanonDefeatRule(state, player))
if multiworld.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: if world.options.goal in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
add_rule(ganon, lambda state: has_triforce_pieces(state, player)) add_rule(ganon, lambda state: has_triforce_pieces(state, player))
elif multiworld.goal[player] == 'ganon_pedestal': elif world.options.goal == 'ganon_pedestal':
add_rule(multiworld.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player)) add_rule(multiworld.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player))
else: else:
add_rule(ganon, lambda state: has_crystals(state, state.multiworld.crystals_needed_for_ganon[player], player)) add_rule(ganon, lambda state: has_crystals(state, state.multiworld.worlds[player].options.crystals_needed_for_ganon, player))
set_rule(multiworld.get_entrance('Ganon Drop', player), lambda state: has_beam_sword(state, player)) # need to damage ganon to get tiles to drop set_rule(multiworld.get_entrance('Ganon Drop', player), lambda state: has_beam_sword(state, player)) # need to damage ganon to get tiles to drop
set_rule(multiworld.get_location('Flute Activation Spot', player), lambda state: state.has('Flute', player)) set_rule(multiworld.get_location('Flute Activation Spot', player), lambda state: state.has('Flute', player))
@@ -722,9 +725,9 @@ def default_rules(world, player):
set_rule(world.get_entrance('Floating Island Mirror Spot', player), lambda state: state.has('Magic Mirror', player)) set_rule(world.get_entrance('Floating Island Mirror Spot', player), lambda state: state.has('Magic Mirror', player))
set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has('Moon Pearl', player) and has_sword(state, player) and has_turtle_rock_medallion(state, player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword required to cast magic (!) set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has('Moon Pearl', player) and has_sword(state, player) and has_turtle_rock_medallion(state, player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword required to cast magic (!)
set_rule(world.get_entrance('Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.open_pyramid[player].to_bool(world, player)) set_rule(world.get_entrance('Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.worlds[player].options.open_pyramid.to_bool(world, player))
if world.swordless[player]: if world.worlds[player].options.swordless:
swordless_rules(world, player) swordless_rules(world, player)
@@ -879,14 +882,14 @@ def inverted_rules(world, player):
set_rule(world.get_entrance('Dark Grassy Lawn Flute', player), lambda state: state.has('Activated Flute', player)) set_rule(world.get_entrance('Dark Grassy Lawn Flute', player), lambda state: state.has('Activated Flute', player))
set_rule(world.get_entrance('Hammer Peg Area Flute', player), lambda state: state.has('Activated Flute', player)) set_rule(world.get_entrance('Hammer Peg Area Flute', player), lambda state: state.has('Activated Flute', player))
set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.open_pyramid[player]) set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.worlds[player].options.open_pyramid)
if world.swordless[player]: if world.worlds[player].options.swordless:
swordless_rules(world, player) swordless_rules(world, player)
def no_glitches_rules(world, player): def no_glitches_rules(world, player):
"""""" """"""
if world.mode[player] == 'inverted': if world.worlds[player].options.mode == 'inverted':
set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Moon Pearl', player) and (state.has('Flippers', player) or can_lift_rocks(state, player))) set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Moon Pearl', player) and (state.has('Flippers', player) or can_lift_rocks(state, player)))
set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Moon Pearl', player) and state.has('Flippers', player)) # can be fake flippered to set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Moon 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('Moon 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('Moon Pearl', player) and state.has('Flippers', player)) # can be fake flippered to
@@ -910,7 +913,7 @@ def no_glitches_rules(world, player):
add_conditional_lamps(world, player) add_conditional_lamps(world, player)
def fake_flipper_rules(world, player): def fake_flipper_rules(world, player):
if world.mode[player] == 'inverted': if world.worlds[player].options.mode == 'inverted':
set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Moon Pearl', player)) set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Moon Pearl', player))
set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Moon Pearl', player)) set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Moon Pearl', player))
set_rule(world.get_entrance('Lake Hylia Island Pier', player), lambda state: state.has('Moon Pearl', player)) set_rule(world.get_entrance('Lake Hylia Island Pier', player), lambda state: state.has('Moon Pearl', player))
@@ -996,7 +999,7 @@ def add_conditional_lamps(world, player):
'Location', True) 'Location', True)
add_conditional_lamp('Palace of Darkness - Dark Basement - Right', 'Palace of Darkness (Entrance)', add_conditional_lamp('Palace of Darkness - Dark Basement - Right', 'Palace of Darkness (Entrance)',
'Location', True) 'Location', True)
if world.mode[player] != 'inverted': if world.worlds[player].options.mode != 'inverted':
add_conditional_lamp('Agahnim 1', 'Agahnims Tower', 'Entrance') add_conditional_lamp('Agahnim 1', 'Agahnims Tower', 'Entrance')
add_conditional_lamp('Castle Tower - Dark Maze', 'Agahnims Tower') add_conditional_lamp('Castle Tower - Dark Maze', 'Agahnims Tower')
add_conditional_lamp('Castle Tower - Dark Archer Key Drop', 'Agahnims Tower') add_conditional_lamp('Castle Tower - Dark Archer Key Drop', 'Agahnims Tower')
@@ -1018,7 +1021,7 @@ def add_conditional_lamps(world, player):
add_conditional_lamp('Eastern Palace - Boss', 'Eastern Palace', 'Location', True) add_conditional_lamp('Eastern Palace - Boss', 'Eastern Palace', 'Location', True)
add_conditional_lamp('Eastern Palace - Prize', 'Eastern Palace', 'Location', True) add_conditional_lamp('Eastern Palace - Prize', 'Eastern Palace', 'Location', True)
if not world.mode[player] == "standard": if not world.worlds[player].options.mode == "standard":
add_lamp_requirement(world, world.get_location('Sewers - Dark Cross', player), player) add_lamp_requirement(world, world.get_location('Sewers - Dark Cross', player), player)
add_lamp_requirement(world, world.get_entrance('Sewers Back Door', player), player) add_lamp_requirement(world, world.get_entrance('Sewers Back Door', player), player)
add_lamp_requirement(world, world.get_entrance('Throne Room', player), player) add_lamp_requirement(world, world.get_entrance('Throne Room', player), player)
@@ -1044,7 +1047,7 @@ def open_rules(world, player):
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4)
and state.has('Big Key (Hyrule Castle)', player) and state.has('Big Key (Hyrule Castle)', player)
and (world.enemy_health[player] in ("easy", "default") and (world.worlds[player].options.enemy_health in ("easy", "default")
or can_kill_most_things(state, player, 1))) or can_kill_most_things(state, player, 1)))
@@ -1058,7 +1061,7 @@ def swordless_rules(world, player):
set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop
if world.mode[player] != 'inverted': if world.worlds[player].options.mode != 'inverted':
set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle
set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has('Moon Pearl', player) and has_turtle_rock_medallion(state, player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has('Moon Pearl', player) and has_turtle_rock_medallion(state, player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword not required to use medallion for opening in swordless (!)
set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has('Moon Pearl', player) and has_misery_mire_medallion(state, player)) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has('Moon Pearl', player) and has_misery_mire_medallion(state, player)) # sword not required to use medallion for opening in swordless (!)
@@ -1084,7 +1087,7 @@ def standard_rules(world, player):
set_rule(world.get_entrance('Links House S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player)) set_rule(world.get_entrance('Links House S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player)) set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
if world.small_key_shuffle[player] != small_key_shuffle.option_universal: if world.worlds[player].options.small_key_shuffle != small_key_shuffle.option_universal:
set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player), set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1) lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1)
and can_kill_most_things(state, player, 2)) and can_kill_most_things(state, player, 2))
@@ -1097,7 +1100,7 @@ def standard_rules(world, player):
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2) lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2)
and state.has('Big Key (Hyrule Castle)', player) and state.has('Big Key (Hyrule Castle)', player)
and (world.enemy_health[player] in ("easy", "default") and (world.worlds[player].options.enemy_health in ("easy", "default")
or can_kill_most_things(state, player, 1))) or can_kill_most_things(state, player, 1)))
set_rule(world.get_location('Sewers - Key Rat Key Drop', player), set_rule(world.get_location('Sewers - Key Rat Key Drop', player),
@@ -1195,15 +1198,15 @@ def set_trock_key_rules(multiworld, player):
return 6 return 6
# If TR is only accessible from the middle, the big key must be further restricted to prevent softlock potential # If TR is only accessible from the middle, the big key must be further restricted to prevent softlock potential
if not can_reach_front and not multiworld.small_key_shuffle[player]: if not can_reach_front and not multiworld.worlds[player].options.small_key_shuffle:
# Must not go in the Big Key Chest - only 1 other chest available and 2+ keys required for all other chests # Must not go in the Big Key Chest - only 1 other chest available and 2+ keys required for all other chests
forbid_item(multiworld.get_location('Turtle Rock - Big Key Chest', player), 'Big Key (Turtle Rock)', player) forbid_item(multiworld.get_location('Turtle Rock - Big Key Chest', player), 'Big Key (Turtle Rock)', player)
if not can_reach_big_chest: if not can_reach_big_chest:
# Must not go in the Chain Chomps chest - only 2 other chests available and 3+ keys required for all other chests # Must not go in the Chain Chomps chest - only 2 other chests available and 3+ keys required for all other chests
forbid_item(multiworld.get_location('Turtle Rock - Chain Chomps', player), 'Big Key (Turtle Rock)', player) forbid_item(multiworld.get_location('Turtle Rock - Chain Chomps', player), 'Big Key (Turtle Rock)', player)
forbid_item(multiworld.get_location('Turtle Rock - Pokey 2 Key Drop', player), 'Big Key (Turtle Rock)', player) forbid_item(multiworld.get_location('Turtle Rock - Pokey 2 Key Drop', player), 'Big Key (Turtle Rock)', player)
if multiworld.accessibility[player] == 'full': if multiworld.worlds[player].options.accessibility == 'full':
if multiworld.big_key_shuffle[player] and can_reach_big_chest: if multiworld.worlds[player].options.big_key_shuffle and can_reach_big_chest:
# Must not go in the dungeon - all 3 available chests (Chomps, Big Chest, Crystaroller) must be keys to access laser bridge, and the big key is required first # Must not go in the dungeon - all 3 available chests (Chomps, Big Chest, Crystaroller) must be keys to access laser bridge, and the big key is required first
for location in ['Turtle Rock - Chain Chomps', 'Turtle Rock - Compass Chest', for location in ['Turtle Rock - Chain Chomps', 'Turtle Rock - Compass Chest',
'Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop', 'Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop',
@@ -1216,7 +1219,7 @@ def set_trock_key_rules(multiworld, player):
location.place_locked_item(item) location.place_locked_item(item)
toss_junk_item(multiworld, player) toss_junk_item(multiworld, player)
if multiworld.accessibility[player] != 'full': if multiworld.worlds[player].options.accessibility != 'full':
set_always_allow(multiworld.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player set_always_allow(multiworld.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player
and state.can_reach(state.multiworld.get_region('Turtle Rock (Second Section)', player))) and state.can_reach(state.multiworld.get_region('Turtle Rock (Second Section)', player)))
@@ -1683,7 +1686,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool):
def get_rule_to_add(region, location = None, connecting_entrance = None): def get_rule_to_add(region, location = None, connecting_entrance = None):
# In OWG, a location can potentially be superbunny-mirror accessible or # In OWG, a location can potentially be superbunny-mirror accessible or
# bunny revival accessible. # bunny revival accessible.
if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic']: if world.worlds[player].options.glitches_required in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic']:
if region.name == 'Swamp Palace (Entrance)': # Need to 0hp revive - not in logic if region.name == 'Swamp Palace (Entrance)': # Need to 0hp revive - not in logic
return lambda state: state.has('Moon Pearl', player) return lambda state: state.has('Moon Pearl', player)
if region.name == 'Tower of Hera (Bottom)': # Need to hit the crystal switch if region.name == 'Tower of Hera (Bottom)': # Need to hit the crystal switch
@@ -1723,7 +1726,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool):
seen.add(new_region) seen.add(new_region)
if not is_link(new_region): if not is_link(new_region):
# For glitch rulesets, establish superbunny and revival rules. # For glitch rulesets, establish superbunny and revival rules.
if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and entrance.name not in OverworldGlitchRules.get_invalid_bunny_revival_dungeons(): if world.worlds[player].options.glitches_required in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and entrance.name not in OverworldGlitchRules.get_invalid_bunny_revival_dungeons():
if region.name in OverworldGlitchRules.get_sword_required_superbunny_mirror_regions(): if region.name in OverworldGlitchRules.get_sword_required_superbunny_mirror_regions():
possible_options.append(lambda state: path_to_access_rule(new_path, entrance) and state.has('Magic Mirror', player) and has_sword(state, player)) possible_options.append(lambda state: path_to_access_rule(new_path, entrance) and state.has('Magic Mirror', player) and has_sword(state, player))
elif (region.name in OverworldGlitchRules.get_boots_required_superbunny_mirror_regions() elif (region.name in OverworldGlitchRules.get_boots_required_superbunny_mirror_regions()
@@ -1760,7 +1763,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool):
# Add requirements for all locations that are actually in the dark world, except those available to the bunny, including dungeon revival # Add requirements for all locations that are actually in the dark world, except those available to the bunny, including dungeon revival
for entrance in world.get_entrances(player): for entrance in world.get_entrances(player):
if is_bunny(entrance.connected_region): if is_bunny(entrance.connected_region):
if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] : if world.worlds[player].options.glitches_required in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] :
if entrance.connected_region.type == LTTPRegionType.Dungeon: if entrance.connected_region.type == LTTPRegionType.Dungeon:
if entrance.parent_region.type != LTTPRegionType.Dungeon and entrance.connected_region.name in OverworldGlitchRules.get_invalid_bunny_revival_dungeons(): if entrance.parent_region.type != LTTPRegionType.Dungeon and entrance.connected_region.name in OverworldGlitchRules.get_invalid_bunny_revival_dungeons():
add_rule(entrance, get_rule_to_add(entrance.connected_region, None, entrance)) add_rule(entrance, get_rule_to_add(entrance.connected_region, None, entrance))
@@ -1768,7 +1771,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool):
if entrance.connected_region.name == 'Turtle Rock (Entrance)': if entrance.connected_region.name == 'Turtle Rock (Entrance)':
add_rule(world.get_entrance('Turtle Rock Entrance Gap', player), get_rule_to_add(entrance.connected_region, None, entrance)) add_rule(world.get_entrance('Turtle Rock Entrance Gap', player), get_rule_to_add(entrance.connected_region, None, entrance))
for location in entrance.connected_region.locations: for location in entrance.connected_region.locations:
if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): if world.worlds[player].options.glitches_required in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances():
continue continue
if location.name in bunny_accessible_locations: if location.name in bunny_accessible_locations:
continue continue

View File

@@ -168,7 +168,7 @@ def push_shop_inventories(multiworld):
for location in shop_slots: for location in shop_slots:
item_name = location.item.name item_name = location.item.name
# Retro Bow arrows will already have been pushed # Retro Bow arrows will already have been pushed
if (not multiworld.retro_bow[location.player]) or ((item_name, location.item.player) if (not multiworld.worlds[location.player].options.retro_bow) or ((item_name, location.item.player)
!= ("Single Arrow", location.player)): != ("Single Arrow", location.player)):
location.shop.push_inventory(location.shop_slot, item_name, location.shop.push_inventory(location.shop_slot, item_name,
round(location.shop_price * get_price_modifier(location.item)), round(location.shop_price * get_price_modifier(location.item)),
@@ -185,36 +185,36 @@ def push_shop_inventories(multiworld):
def create_shops(multiworld, player: int): def create_shops(multiworld, player: int):
from .Options import RandomizeShopInventories from .Options import RandomizeShopInventories
player_shop_table = shop_table.copy() player_shop_table = shop_table.copy()
if multiworld.include_witch_hut[player]: if multiworld.worlds[player].options.include_witch_hut:
player_shop_table["Potion Shop"] = player_shop_table["Potion Shop"]._replace(locked=False) player_shop_table["Potion Shop"] = player_shop_table["Potion Shop"]._replace(locked=False)
dynamic_shop_slots = total_dynamic_shop_slots + 3 dynamic_shop_slots = total_dynamic_shop_slots + 3
else: else:
dynamic_shop_slots = total_dynamic_shop_slots dynamic_shop_slots = total_dynamic_shop_slots
if multiworld.shuffle_capacity_upgrades[player]: if multiworld.worlds[player].options.shuffle_capacity_upgrades:
player_shop_table["Capacity Upgrade"] = player_shop_table["Capacity Upgrade"]._replace(locked=False) player_shop_table["Capacity Upgrade"] = player_shop_table["Capacity Upgrade"]._replace(locked=False)
num_slots = min(dynamic_shop_slots, multiworld.shop_item_slots[player]) num_slots = min(dynamic_shop_slots, multiworld.worlds[player].options.shop_item_slots)
single_purchase_slots: List[bool] = [True] * num_slots + [False] * (dynamic_shop_slots - num_slots) single_purchase_slots: List[bool] = [True] * num_slots + [False] * (dynamic_shop_slots - num_slots)
multiworld.random.shuffle(single_purchase_slots) multiworld.random.shuffle(single_purchase_slots)
if multiworld.randomize_shop_inventories[player]: if multiworld.worlds[player].options.randomize_shop_inventories:
default_shop_table = [i for l in default_shop_table = [i for l in
[shop_generation_types[x] for x in ['arrows', 'bombs', 'potions', 'shields', 'bottle'] if [shop_generation_types[x] for x in ['arrows', 'bombs', 'potions', 'shields', 'bottle'] if
not multiworld.retro_bow[player] or x != 'arrows'] for i in l] not multiworld.worlds[player].options.retro_bow or x != 'arrows'] for i in l]
new_basic_shop = multiworld.random.sample(default_shop_table, k=3) new_basic_shop = multiworld.random.sample(default_shop_table, k=3)
new_dark_shop = multiworld.random.sample(default_shop_table, k=3) new_dark_shop = multiworld.random.sample(default_shop_table, k=3)
for name, shop in player_shop_table.items(): for name, shop in player_shop_table.items():
typ, shop_id, keeper, custom, locked, items, sram_offset = shop typ, shop_id, keeper, custom, locked, items, sram_offset = shop
if not locked: if not locked:
new_items = multiworld.random.sample(default_shop_table, k=len(items)) new_items = multiworld.random.sample(default_shop_table, k=len(items))
if multiworld.randomize_shop_inventories[player] == RandomizeShopInventories.option_randomize_by_shop_type: if multiworld.worlds[player].options.randomize_shop_inventories == RandomizeShopInventories.option_randomize_by_shop_type:
if items == _basic_shop_defaults: if items == _basic_shop_defaults:
new_items = new_basic_shop new_items = new_basic_shop
elif items == _dark_world_shop_defaults: elif items == _dark_world_shop_defaults:
new_items = new_dark_shop new_items = new_dark_shop
keeper = multiworld.random.choice([0xA0, 0xC1, 0xFF]) keeper = multiworld.random.choice([0xA0, 0xC1, 0xFF])
player_shop_table[name] = ShopData(typ, shop_id, keeper, custom, locked, new_items, sram_offset) player_shop_table[name] = ShopData(typ, shop_id, keeper, custom, locked, new_items, sram_offset)
if multiworld.mode[player] == "inverted": if multiworld.worlds[player].options.mode == "inverted":
# make sure that blue potion is available in inverted, special case locked = None; lock when done. # make sure that blue potion is available in inverted, special case locked = None; lock when done.
player_shop_table["Dark Lake Hylia Shop"] = \ player_shop_table["Dark Lake Hylia Shop"] = \
player_shop_table["Dark Lake Hylia Shop"]._replace(items=_inverted_hylia_shop_defaults, locked=None) player_shop_table["Dark Lake Hylia Shop"]._replace(items=_inverted_hylia_shop_defaults, locked=None)
@@ -237,7 +237,7 @@ def create_shops(multiworld, player: int):
add_rule(loc, lambda state, spot=loc: shop_price_rules(state, player, spot)) add_rule(loc, lambda state, spot=loc: shop_price_rules(state, player, spot))
loc.shop = shop loc.shop = shop
loc.shop_slot = index loc.shop_slot = index
if ((not (multiworld.shuffle_capacity_upgrades[player] and type == ShopType.UpgradeShop)) if ((not (multiworld.worlds[player].options.shuffle_capacity_upgrades and type == ShopType.UpgradeShop))
and not single_purchase_slots.pop()): and not single_purchase_slots.pop()):
loc.shop_slot_disabled = True loc.shop_slot_disabled = True
loc.locked = True loc.locked = True
@@ -309,18 +309,18 @@ def set_up_shops(multiworld, player: int):
from .Options import small_key_shuffle from .Options import small_key_shuffle
# TODO: move hard+ mode changes for shields here, utilizing the new shops # TODO: move hard+ mode changes for shields here, utilizing the new shops
if multiworld.retro_bow[player]: if multiworld.worlds[player].options.retro_bow:
rss = multiworld.get_region('Red Shield Shop', player).shop rss = multiworld.get_region('Red Shield Shop', player).shop
replacement_items = [['Red Potion', 150], ['Green Potion', 75], ['Blue Potion', 200], ['Bombs (10)', 50], replacement_items = [['Red Potion', 150], ['Green Potion', 75], ['Blue Potion', 200], ['Bombs (10)', 50],
['Blue Shield', 50], ['Small Heart', ['Blue Shield', 50], ['Small Heart',
10]] # Can't just replace the single arrow with 10 arrows as retro doesn't need them. 10]] # Can't just replace the single arrow with 10 arrows as retro doesn't need them.
if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal:
replacement_items.append(['Small Key (Universal)', 100]) replacement_items.append(['Small Key (Universal)', 100])
replacement_item = multiworld.random.choice(replacement_items) replacement_item = multiworld.random.choice(replacement_items)
rss.add_inventory(2, 'Single Arrow', 80, 1, replacement_item[0], replacement_item[1]) rss.add_inventory(2, 'Single Arrow', 80, 1, replacement_item[0], replacement_item[1])
rss.locked = True rss.locked = True
if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal or multiworld.retro_bow[player]: if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal or multiworld.worlds[player].options.retro_bow:
for shop in multiworld.random.sample([s for s in multiworld.shops if for shop in multiworld.random.sample([s for s in multiworld.shops if
s.custom and not s.locked and s.type == ShopType.Shop s.custom and not s.locked and s.type == ShopType.Shop
and s.region.player == player], 5): and s.region.player == player], 5):
@@ -328,19 +328,19 @@ def set_up_shops(multiworld, player: int):
slots = [0, 1, 2] slots = [0, 1, 2]
multiworld.random.shuffle(slots) multiworld.random.shuffle(slots)
slots = iter(slots) slots = iter(slots)
if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal:
shop.add_inventory(next(slots), 'Small Key (Universal)', 100) shop.add_inventory(next(slots), 'Small Key (Universal)', 100)
if multiworld.retro_bow[player]: if multiworld.worlds[player].options.retro_bow:
shop.push_inventory(next(slots), 'Single Arrow', 80) shop.push_inventory(next(slots), 'Single Arrow', 80)
if multiworld.shuffle_capacity_upgrades[player]: if multiworld.worlds[player].options.shuffle_capacity_upgrades:
for shop in multiworld.shops: for shop in multiworld.shops:
if shop.type == ShopType.UpgradeShop and shop.region.player == player and \ if shop.type == ShopType.UpgradeShop and shop.region.player == player and \
shop.region.name == "Capacity Upgrade": shop.region.name == "Capacity Upgrade":
shop.clear_inventory() shop.clear_inventory()
if (multiworld.shuffle_shop_inventories[player] or multiworld.randomize_shop_prices[player] if (multiworld.worlds[player].options.shuffle_shop_inventories or multiworld.worlds[player].options.randomize_shop_prices
or multiworld.randomize_cost_types[player]): or multiworld.worlds[player].options.randomize_cost_types):
shops = [] shops = []
total_inventory = [] total_inventory = []
for shop in multiworld.shops: for shop in multiworld.shops:
@@ -352,7 +352,7 @@ def set_up_shops(multiworld, player: int):
for item in total_inventory: for item in total_inventory:
item["price_type"], item["price"] = get_price(multiworld, item, player) item["price_type"], item["price"] = get_price(multiworld, item, player)
if multiworld.shuffle_shop_inventories[player]: if multiworld.worlds[player].options.shuffle_shop_inventories:
multiworld.random.shuffle(total_inventory) multiworld.random.shuffle(total_inventory)
i = 0 i = 0
@@ -434,39 +434,39 @@ def get_price(multiworld, item, player: int, price_type=None):
price_types = [price_type] price_types = [price_type]
else: else:
price_types = [ShopPriceType.Rupees] # included as a chance to not change price price_types = [ShopPriceType.Rupees] # included as a chance to not change price
if multiworld.randomize_cost_types[player]: if multiworld.worlds[player].options.randomize_cost_types:
price_types += [ price_types += [
ShopPriceType.Hearts, ShopPriceType.Hearts,
ShopPriceType.Bombs, ShopPriceType.Bombs,
ShopPriceType.Magic, ShopPriceType.Magic,
] ]
if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal:
if item and item["item"] == "Small Key (Universal)": if item and item["item"] == "Small Key (Universal)":
price_types = [ShopPriceType.Rupees, ShopPriceType.Magic] # no logical requirements for repeatable keys price_types = [ShopPriceType.Rupees, ShopPriceType.Magic] # no logical requirements for repeatable keys
else: else:
price_types.append(ShopPriceType.Keys) price_types.append(ShopPriceType.Keys)
if multiworld.retro_bow[player]: if multiworld.worlds[player].options.retro_bow:
if item and item["item"] == "Single Arrow": if item and item["item"] == "Single Arrow":
price_types = [ShopPriceType.Rupees, ShopPriceType.Magic] # no logical requirements for arrows price_types = [ShopPriceType.Rupees, ShopPriceType.Magic] # no logical requirements for arrows
else: else:
price_types.append(ShopPriceType.Arrows) price_types.append(ShopPriceType.Arrows)
diff = multiworld.item_pool[player].value diff = multiworld.worlds[player].options.item_pool.value
if item: if item:
# This is for a shop's regular inventory, the item is already determined, and we will decide the price here # This is for a shop's regular inventory, the item is already determined, and we will decide the price here
price = item["price"] price = item["price"]
if multiworld.randomize_shop_prices[player]: if multiworld.worlds[player].options.randomize_shop_prices:
adjust = 2 if price < 100 else 5 adjust = 2 if price < 100 else 5
price = int((price / adjust) * (0.5 + multiworld.per_slot_randoms[player].random() * 1.5)) * adjust price = int((price / adjust) * (0.5 + multiworld.worlds[player].random.random() * 1.5)) * adjust
multiworld.per_slot_randoms[player].shuffle(price_types) multiworld.worlds[player].random.shuffle(price_types)
for p_type in price_types: for p_type in price_types:
if any(x in item['item'] for x in price_blacklist[p_type]): if any(x in item['item'] for x in price_blacklist[p_type]):
continue continue
return p_type, price_chart[p_type](price, diff) return p_type, price_chart[p_type](price, diff)
else: else:
# This is an AP location and the price will be adjusted after an item is shuffled into it # This is an AP location and the price will be adjusted after an item is shuffled into it
p_type = multiworld.per_slot_randoms[player].choice(price_types) p_type = multiworld.worlds[player].random.choice(price_types)
return p_type, price_chart[p_type](min(int(multiworld.per_slot_randoms[player].randint(8, 56) return p_type, price_chart[p_type](min(int(multiworld.worlds[player].random.randint(8, 56)
* multiworld.shop_price_modifier[player] / 100) * 5, 9999), diff) * multiworld.worlds[player].options.shop_price_modifier / 100) * 5, 9999), diff)
def shop_price_rules(state: CollectionState, player: int, location: ALttPLocation): def shop_price_rules(state: CollectionState, player: int, location: ALttPLocation):

View File

@@ -6,7 +6,7 @@ def is_not_bunny(state: CollectionState, region: LTTPRegion, player: int) -> boo
if state.has('Moon Pearl', player): if state.has('Moon Pearl', player):
return True return True
return region.is_light_world if state.multiworld.mode[player] != 'inverted' else region.is_dark_world return region.is_light_world if state.multiworld.worlds[player].options.mode != 'inverted' else region.is_dark_world
def can_bomb_clip(state: CollectionState, region: LTTPRegion, player: int) -> bool: def can_bomb_clip(state: CollectionState, region: LTTPRegion, player: int) -> bool:
@@ -24,7 +24,7 @@ def can_buy(state: CollectionState, item: str, player: int) -> bool:
def can_shoot_arrows(state: CollectionState, player: int, count: int = 0) -> bool: def can_shoot_arrows(state: CollectionState, player: int, count: int = 0) -> bool:
if state.multiworld.retro_bow[player]: if state.multiworld.worlds[player].options.retro_bow:
return (state.has('Bow', player) or state.has('Silver Bow', player)) and can_buy(state, 'Single Arrow', player) return (state.has('Bow', player) or state.has('Silver Bow', player)) and can_buy(state, 'Single Arrow', player)
return (state.has('Bow', player) or state.has('Silver Bow', player)) and can_hold_arrows(state, player, count) return (state.has('Bow', player) or state.has('Silver Bow', player)) and can_hold_arrows(state, player, count)
@@ -74,9 +74,9 @@ def can_extend_magic(state: CollectionState, player: int, smallmagic: int = 16,
elif state.has('Magic Upgrade (1/2)', player): elif state.has('Magic Upgrade (1/2)', player):
basemagic = 16 basemagic = 16
if can_buy_unlimited(state, 'Green Potion', player) or can_buy_unlimited(state, 'Blue Potion', player): if can_buy_unlimited(state, 'Green Potion', player) or can_buy_unlimited(state, 'Blue Potion', player):
if state.multiworld.item_functionality[player] == 'hard' and not fullrefill: if state.multiworld.worlds[player].options.item_functionality == 'hard' and not fullrefill:
basemagic = basemagic + int(basemagic * 0.5 * bottle_count(state, player)) basemagic = basemagic + int(basemagic * 0.5 * bottle_count(state, player))
elif state.multiworld.item_functionality[player] == 'expert' and not fullrefill: elif state.multiworld.worlds[player].options.item_functionality == 'expert' and not fullrefill:
basemagic = basemagic + int(basemagic * 0.25 * bottle_count(state, player)) basemagic = basemagic + int(basemagic * 0.25 * bottle_count(state, player))
else: else:
basemagic = basemagic + basemagic * bottle_count(state, player) basemagic = basemagic + basemagic * bottle_count(state, player)
@@ -99,12 +99,12 @@ def can_hold_arrows(state: CollectionState, player: int, quantity: int):
def can_use_bombs(state: CollectionState, player: int, quantity: int = 1) -> bool: def can_use_bombs(state: CollectionState, player: int, quantity: int = 1) -> bool:
bombs = 0 if state.multiworld.bombless_start[player] else 10 bombs = 0 if state.multiworld.worlds[player].options.bombless_start else 10
bombs += ((state.count("Bomb Upgrade (+5)", player) * 5) + (state.count("Bomb Upgrade (+10)", player) * 10) bombs += ((state.count("Bomb Upgrade (+5)", player) * 5) + (state.count("Bomb Upgrade (+10)", player) * 10)
+ (state.count("Bomb Upgrade (50)", player) * 50)) + (state.count("Bomb Upgrade (50)", player) * 50))
# Bomb Upgrade (+5) beyond the 6th gives +10 # Bomb Upgrade (+5) beyond the 6th gives +10
bombs += max(0, ((state.count("Bomb Upgrade (+5)", player) - 6) * 10)) bombs += max(0, ((state.count("Bomb Upgrade (+5)", player) - 6) * 10))
if (not state.multiworld.shuffle_capacity_upgrades[player]) and state.has("Capacity Upgrade Shop", player): if (not state.multiworld.worlds[player].options.shuffle_capacity_upgrades) and state.has("Capacity Upgrade Shop", player):
bombs += 40 bombs += 40
return bombs >= min(quantity, 50) return bombs >= min(quantity, 50)
@@ -120,7 +120,7 @@ def can_activate_crystal_switch(state: CollectionState, player: int) -> bool:
def can_kill_most_things(state: CollectionState, player: int, enemies: int = 5) -> bool: def can_kill_most_things(state: CollectionState, player: int, enemies: int = 5) -> bool:
if state.multiworld.enemy_shuffle[player]: if state.multiworld.worlds[player].options.enemy_shuffle:
# I don't fully understand Enemizer's logic for placing enemies in spots where they need to be killable, if any. # I don't fully understand Enemizer's logic for placing enemies in spots where they need to be killable, if any.
# Just go with maximal requirements for now. # Just go with maximal requirements for now.
return (has_melee_weapon(state, player) return (has_melee_weapon(state, player)
@@ -135,7 +135,7 @@ def can_kill_most_things(state: CollectionState, player: int, enemies: int = 5)
or (state.has('Cane of Byrna', player) and (enemies < 6 or can_extend_magic(state, player))) or (state.has('Cane of Byrna', player) and (enemies < 6 or can_extend_magic(state, player)))
or can_shoot_arrows(state, player) or can_shoot_arrows(state, player)
or state.has('Fire Rod', player) or state.has('Fire Rod', player)
or (state.multiworld.enemy_health[player] in ("easy", "default") or (state.multiworld.worlds[player].options.enemy_health in ("easy", "default")
and can_use_bombs(state, player, enemies * 4))) and can_use_bombs(state, player, enemies * 4)))
@@ -152,7 +152,7 @@ def can_get_good_bee(state: CollectionState, player: int) -> bool:
def can_retrieve_tablet(state: CollectionState, player: int) -> bool: def can_retrieve_tablet(state: CollectionState, player: int) -> bool:
return state.has('Book of Mudora', player) and (has_beam_sword(state, player) or return state.has('Book of Mudora', player) and (has_beam_sword(state, player) or
(state.multiworld.swordless[player] and (state.multiworld.worlds[player].options.swordless and
state.has("Hammer", player))) state.has("Hammer", player)))
@@ -179,7 +179,7 @@ def has_fire_source(state: CollectionState, player: int) -> bool:
def can_melt_things(state: CollectionState, player: int) -> bool: def can_melt_things(state: CollectionState, player: int) -> bool:
return state.has('Fire Rod', player) or \ return state.has('Fire Rod', player) or \
(state.has('Bombos', player) and (state.has('Bombos', player) and
(state.multiworld.swordless[player] or (state.multiworld.worlds[player].options.swordless or
has_sword(state, player))) has_sword(state, player)))
@@ -192,19 +192,19 @@ def has_turtle_rock_medallion(state: CollectionState, player: int) -> bool:
def can_boots_clip_lw(state: CollectionState, player: int) -> bool: def can_boots_clip_lw(state: CollectionState, player: int) -> bool:
if state.multiworld.mode[player] == 'inverted': if state.multiworld.worlds[player].options.mode == 'inverted':
return state.has('Pegasus Boots', player) and state.has('Moon Pearl', player) return state.has('Pegasus Boots', player) and state.has('Moon Pearl', player)
return state.has('Pegasus Boots', player) return state.has('Pegasus Boots', player)
def can_boots_clip_dw(state: CollectionState, player: int) -> bool: def can_boots_clip_dw(state: CollectionState, player: int) -> bool:
if state.multiworld.mode[player] != 'inverted': if state.multiworld.worlds[player].options.mode != 'inverted':
return state.has('Pegasus Boots', player) and state.has('Moon Pearl', player) return state.has('Pegasus Boots', player) and state.has('Moon Pearl', player)
return state.has('Pegasus Boots', player) return state.has('Pegasus Boots', player)
def can_get_glitched_speed_dw(state: CollectionState, player: int) -> bool: def can_get_glitched_speed_dw(state: CollectionState, player: int) -> bool:
rules = [state.has('Pegasus Boots', player), any([state.has('Hookshot', player), has_sword(state, player)])] rules = [state.has('Pegasus Boots', player), any([state.has('Hookshot', player), has_sword(state, player)])]
if state.multiworld.mode[player] != 'inverted': if state.multiworld.worlds[player].options.mode != 'inverted':
rules.append(state.has('Moon Pearl', player)) rules.append(state.has('Moon Pearl', player))
return all(rules) return all(rules)

View File

@@ -88,12 +88,12 @@ def underworld_glitches_rules(world, player):
# We need to be able to s+q to old man, then go to either Mire or Hera at either Hera or GT. # We need to be able to s+q to old man, then go to either Mire or Hera at either Hera or GT.
# First we require a certain type of entrance shuffle, then build the rule from its pieces. # First we require a certain type of entrance shuffle, then build the rule from its pieces.
if not world.worlds[player].swamp_patch_required: if not world.worlds[player].swamp_patch_required:
if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
rule_map = { rule_map = {
'Misery Mire (Entrance)': (lambda state: True), 'Misery Mire (Entrance)': (lambda state: True),
'Tower of Hera (Bottom)': (lambda state: state.can_reach('Tower of Hera Big Key Door', 'Entrance', player)) 'Tower of Hera (Bottom)': (lambda state: state.can_reach('Tower of Hera Big Key Door', 'Entrance', player))
} }
inverted = world.mode[player] == 'inverted' inverted = world.worlds[player].options.mode == 'inverted'
hera_rule = lambda state: (state.has('Moon Pearl', player) or not inverted) and \ hera_rule = lambda state: (state.has('Moon Pearl', player) or not inverted) and \
rule_map.get(world.get_entrance('Tower of Hera', player).connected_region.name, lambda state: False)(state) rule_map.get(world.get_entrance('Tower of Hera', player).connected_region.name, lambda state: False)(state)
gt_rule = lambda state: (state.has('Moon Pearl', player) or inverted) and \ gt_rule = lambda state: (state.has('Moon Pearl', player) or inverted) and \

View File

@@ -313,74 +313,62 @@ class ALTTPWorld(World):
break break
def generate_early(self): def generate_early(self):
# write old options
import dataclasses
is_first = self.player == min(self.multiworld.get_game_players(self.game))
for field in dataclasses.fields(self.options_dataclass):
if is_first:
setattr(self.multiworld, field.name, {})
getattr(self.multiworld, field.name)[self.player] = getattr(self.options, field.name)
# end of old options re-establisher
player = self.player
multiworld = self.multiworld multiworld = self.multiworld
self.fix_trock_doors = (multiworld.entrance_shuffle[player] != 'vanilla' self.fix_trock_doors = (self.options.entrance_shuffle != 'vanilla' or self.options.mode == 'inverted')
or multiworld.mode[player] == 'inverted') self.fix_skullwoods_exit = self.options.entrance_shuffle not in ['vanilla', 'simple', 'restricted', 'dungeons_simple']
self.fix_skullwoods_exit = multiworld.entrance_shuffle[player] not in ['vanilla', 'simple', 'restricted', self.fix_palaceofdarkness_exit = self.options.entrance_shuffle not in ['dungeons_simple', 'vanilla', 'simple', 'restricted']
'dungeons_simple'] self.fix_trock_exit = self.options.entrance_shuffle not in ['vanilla', 'simple', 'restricted', 'dungeons_simple']
self.fix_palaceofdarkness_exit = multiworld.entrance_shuffle[player] not in ['dungeons_simple', 'vanilla',
'simple', 'restricted']
self.fix_trock_exit = multiworld.entrance_shuffle[player] not in ['vanilla', 'simple', 'restricted',
'dungeons_simple']
# fairy bottle fills # fairy bottle fills
bottle_options = [ bottle_options = [
"Bottle (Red Potion)", "Bottle (Green Potion)", "Bottle (Blue Potion)", "Bottle (Red Potion)", "Bottle (Green Potion)", "Bottle (Blue Potion)",
"Bottle (Bee)", "Bottle (Good Bee)" "Bottle (Bee)", "Bottle (Good Bee)"
] ]
if multiworld.item_pool[player] not in ["hard", "expert"]: if self.options.item_pool not in ["hard", "expert"]:
bottle_options.append("Bottle (Fairy)") bottle_options.append("Bottle (Fairy)")
self.waterfall_fairy_bottle_fill = self.random.choice(bottle_options) self.waterfall_fairy_bottle_fill = self.random.choice(bottle_options)
self.pyramid_fairy_bottle_fill = self.random.choice(bottle_options) self.pyramid_fairy_bottle_fill = self.random.choice(bottle_options)
if multiworld.mode[player] == 'standard': if self.options.mode == 'standard':
if multiworld.small_key_shuffle[player]: if self.options.small_key_shuffle:
if (multiworld.small_key_shuffle[player] not in if (self.options.small_key_shuffle not in
(small_key_shuffle.option_universal, small_key_shuffle.option_own_dungeons, (small_key_shuffle.option_universal, small_key_shuffle.option_own_dungeons,
small_key_shuffle.option_start_with)): small_key_shuffle.option_start_with)):
self.multiworld.local_early_items[self.player]["Small Key (Hyrule Castle)"] = 1 self.multiworld.local_early_items[self.player]["Small Key (Hyrule Castle)"] = 1
self.multiworld.local_items[self.player].value.add("Small Key (Hyrule Castle)") self.options.local_items.value.add("Small Key (Hyrule Castle)")
self.multiworld.non_local_items[self.player].value.discard("Small Key (Hyrule Castle)") self.options.non_local_items.value.discard("Small Key (Hyrule Castle)")
if multiworld.big_key_shuffle[player]: if self.options.big_key_shuffle:
self.multiworld.local_items[self.player].value.add("Big Key (Hyrule Castle)") self.options.local_items.value.add("Big Key (Hyrule Castle)")
self.multiworld.non_local_items[self.player].value.discard("Big Key (Hyrule Castle)") self.options.non_local_items.value.discard("Big Key (Hyrule Castle)")
# system for sharing ER layouts # system for sharing ER layouts
self.er_seed = str(multiworld.random.randint(0, 2 ** 64)) self.er_seed = str(multiworld.random.randint(0, 2 ** 64))
if multiworld.entrance_shuffle[player] != "vanilla" and multiworld.entrance_shuffle_seed[player] != "random": if self.options.entrance_shuffle != "vanilla" and self.options.entrance_shuffle_seed != "random":
shuffle = multiworld.entrance_shuffle[player].current_key shuffle = self.options.entrance_shuffle.current_key
if shuffle == "vanilla": if shuffle == "vanilla":
self.er_seed = "vanilla" self.er_seed = "vanilla"
elif (not multiworld.entrance_shuffle_seed[player].value.isdigit()) or multiworld.is_race: elif (not self.options.entrance_shuffle_seed.value.isdigit()) or multiworld.is_race:
self.er_seed = get_same_seed(multiworld, ( self.er_seed = get_same_seed(multiworld, (
shuffle, multiworld.entrance_shuffle_seed[player].value, multiworld.retro_caves[player], multiworld.mode[player], shuffle, self.options.entrance_shuffle_seed.value,
multiworld.glitches_required[player])) self.options.retro_caves,
self.options.mode,
self.options.glitches_required
))
else: # not a race or group seed, use set seed as is. else: # not a race or group seed, use set seed as is.
self.er_seed = int(multiworld.entrance_shuffle_seed[player].value) self.er_seed = int(self.options.entrance_shuffle_seed.value)
elif multiworld.entrance_shuffle[player] == "vanilla": elif self.options.entrance_shuffle == "vanilla":
self.er_seed = "vanilla" self.er_seed = "vanilla"
for dungeon_item in ["small_key_shuffle", "big_key_shuffle", "compass_shuffle", "map_shuffle"]: for dungeon_item in ["small_key_shuffle", "big_key_shuffle", "compass_shuffle", "map_shuffle"]:
option = getattr(multiworld, dungeon_item)[player] option = getattr(self.options, dungeon_item)
if option == "own_world": if option == "own_world":
multiworld.local_items[player].value |= self.item_name_groups[option.item_name_group] self.options.local_items.value |= self.item_name_groups[option.item_name_group]
elif option == "different_world": elif option == "different_world":
multiworld.non_local_items[player].value |= self.item_name_groups[option.item_name_group] self.options.non_local_items.value |= self.item_name_groups[option.item_name_group]
if multiworld.mode[player] == "standard": if self.options.mode == "standard":
multiworld.non_local_items[player].value -= {"Small Key (Hyrule Castle)"} self.options.non_local_items.value -= {"Small Key (Hyrule Castle)"}
elif option.in_dungeon: elif option.in_dungeon:
self.dungeon_local_item_names |= self.item_name_groups[option.item_name_group] self.dungeon_local_item_names |= self.item_name_groups[option.item_name_group]
if option == "original_dungeon": if option == "original_dungeon":
@@ -388,15 +376,15 @@ class ALTTPWorld(World):
else: else:
self.options.local_items.value |= self.dungeon_local_item_names self.options.local_items.value |= self.dungeon_local_item_names
self.difficulty_requirements = difficulties[multiworld.item_pool[player].current_key] self.difficulty_requirements = difficulties[self.options.item_pool.current_key]
# enforce pre-defined local items. # enforce pre-defined local items.
if multiworld.goal[player] in ["local_triforce_hunt", "local_ganon_triforce_hunt"]: if self.options.goal in ["local_triforce_hunt", "local_ganon_triforce_hunt"]:
multiworld.local_items[player].value.add('Triforce Piece') self.options.local_items.value.add('Triforce Piece')
# Not possible to place crystals outside boss prizes yet (might as well make it consistent with pendants too). # Not possible to place crystals outside boss prizes yet (might as well make it consistent with pendants too).
multiworld.non_local_items[player].value -= item_name_groups['Pendants'] self.options.non_local_items.value -= item_name_groups['Pendants']
multiworld.non_local_items[player].value -= item_name_groups['Crystals'] self.options.non_local_items.value -= item_name_groups['Crystals']
create_dungeons = create_dungeons create_dungeons = create_dungeons
@@ -404,15 +392,15 @@ class ALTTPWorld(World):
player = self.player player = self.player
multiworld = self.multiworld multiworld = self.multiworld
if multiworld.mode[player] != 'inverted': if self.options.mode != 'inverted':
create_regions(multiworld, player) create_regions(multiworld, player)
else: else:
create_inverted_regions(multiworld, player) create_inverted_regions(multiworld, player)
create_shops(multiworld, player) create_shops(multiworld, player)
self.create_dungeons() self.create_dungeons()
if (multiworld.glitches_required[player] not in ["no_glitches", "minor_glitches"] and if (self.options.glitches_required not in ["no_glitches", "minor_glitches"] and
multiworld.entrance_shuffle[player] in [ self.options.entrance_shuffle in [
"vanilla", "dungeons_simple", "dungeons_full", "simple", "restricted", "full"]): "vanilla", "dungeons_simple", "dungeons_full", "simple", "restricted", "full"]):
self.fix_fake_world = False self.fix_fake_world = False
@@ -420,7 +408,7 @@ class ALTTPWorld(World):
old_random = multiworld.random old_random = multiworld.random
multiworld.random = random.Random(self.er_seed) multiworld.random = random.Random(self.er_seed)
if multiworld.mode[player] != 'inverted': if self.options.mode != 'inverted':
link_entrances(multiworld, player) link_entrances(multiworld, player)
mark_light_world_regions(multiworld, player) mark_light_world_regions(multiworld, player)
else: else:
@@ -505,8 +493,9 @@ class ALTTPWorld(World):
if state.has('Silver Bow', item.player): if state.has('Silver Bow', item.player):
return return
elif state.has('Bow', item.player) and (self.difficulty_requirements.progressive_bow_limit >= 2 elif state.has('Bow', item.player) and (self.difficulty_requirements.progressive_bow_limit >= 2
or self.multiworld.glitches_required[self.player] == 'no_glitches' or self.options.glitches_required == 'no_glitches'
or self.multiworld.swordless[self.player]): # modes where silver bow is always required for ganon or self.options.swordless):
# modes where silver bow is always required for ganon
return 'Silver Bow' return 'Silver Bow'
elif self.difficulty_requirements.progressive_bow_limit >= 1: elif self.difficulty_requirements.progressive_bow_limit >= 1:
return 'Bow' return 'Bow'
@@ -549,9 +538,9 @@ class ALTTPWorld(World):
break break
else: else:
raise FillError('Unable to place dungeon prizes') raise FillError('Unable to place dungeon prizes')
if world.mode[player] == 'standard' and world.small_key_shuffle[player] \ if self.options.mode == 'standard' and self.options.small_key_shuffle \
and world.small_key_shuffle[player] != small_key_shuffle.option_universal and \ and self.options.small_key_shuffle != small_key_shuffle.option_universal and \
world.small_key_shuffle[player] != small_key_shuffle.option_own_dungeons: self.options.small_key_shuffle != small_key_shuffle.option_own_dungeons:
world.local_early_items[player]["Small Key (Hyrule Castle)"] = 1 world.local_early_items[player]["Small Key (Hyrule Castle)"] = 1
@classmethod @classmethod
@@ -592,27 +581,27 @@ class ALTTPWorld(World):
multiworld.spoiler.hashes[player] = get_hash_string(rom.hash) multiworld.spoiler.hashes[player] = get_hash_string(rom.hash)
palettes_options = { palettes_options = {
'dungeon': multiworld.uw_palettes[player], 'dungeon': self.options.uw_palettes,
'overworld': multiworld.ow_palettes[player], 'overworld': self.options.ow_palettes,
'hud': multiworld.hud_palettes[player], 'hud': self.options.hud_palettes,
'sword': multiworld.sword_palettes[player], 'sword': self.options.sword_palettes,
'shield': multiworld.shield_palettes[player], 'shield': self.options.shield_palettes,
# 'link': world.link_palettes[player] # 'link': world.link_palettes[player]
} }
palettes_options = {key: option.current_key for key, option in palettes_options.items()} palettes_options = {key: option.current_key for key, option in palettes_options.items()}
apply_rom_settings(rom, multiworld.heartbeep[player].current_key, apply_rom_settings(rom, self.options.heartbeep.current_key,
multiworld.heartcolor[player].current_key, self.options.heartcolor.current_key,
multiworld.quickswap[player], self.options.quickswap,
multiworld.menuspeed[player].current_key, self.options.menuspeed.current_key,
multiworld.music[player], self.options.music,
multiworld.sprite[player], multiworld.sprite[player],
None, None,
palettes_options, multiworld, player, True, palettes_options, multiworld, player, True,
reduceflashing=multiworld.reduceflashing[player] or multiworld.is_race, reduceflashing=self.options.reduceflashing or multiworld.is_race,
triforcehud=multiworld.triforcehud[player].current_key, triforcehud=self.options.triforcehud.current_key,
deathlink=multiworld.death_link[player], deathlink=self.options.death_link,
allowcollect=multiworld.allow_collect[player]) allowcollect=self.options.allow_collect)
rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc") rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc")
rom.write_to_file(rompath) rom.write_to_file(rompath)
@@ -629,7 +618,7 @@ class ALTTPWorld(World):
@classmethod @classmethod
def stage_extend_hint_information(cls, world, hint_data: typing.Dict[int, typing.Dict[int, str]]): def stage_extend_hint_information(cls, world, hint_data: typing.Dict[int, typing.Dict[int, str]]):
er_hint_data = {player: {} for player in world.get_game_players("A Link to the Past") if er_hint_data = {player: {} for player in world.get_game_players("A Link to the Past") if
world.entrance_shuffle[player] != "vanilla" or world.retro_caves[player]} world.worlds[player].options.entrance_shuffle != "vanilla" or world.worlds[player].options.retro_caves}
for region in world.regions: for region in world.regions:
if region.player in er_hint_data and region.locations: if region.player in er_hint_data and region.locations:
@@ -745,7 +734,7 @@ class ALTTPWorld(World):
f" {self.pyramid_fairy_bottle_fill}") f" {self.pyramid_fairy_bottle_fill}")
spoiler_handle.write(f"\nWaterfall Fairy ({player_name}):" spoiler_handle.write(f"\nWaterfall Fairy ({player_name}):"
f" {self.waterfall_fairy_bottle_fill}") f" {self.waterfall_fairy_bottle_fill}")
if self.multiworld.boss_shuffle[self.player] != "none": if self.options.boss_shuffle != "none":
def create_boss_map() -> typing.Dict: def create_boss_map() -> typing.Dict:
boss_map = { boss_map = {
"Eastern Palace": self.dungeons["Eastern Palace"].boss.name, "Eastern Palace": self.dungeons["Eastern Palace"].boss.name,
@@ -762,7 +751,7 @@ class ALTTPWorld(World):
"Ganons Tower": "Agahnim 2", "Ganons Tower": "Agahnim 2",
"Ganon": "Ganon" "Ganon": "Ganon"
} }
if self.multiworld.mode[self.player] != 'inverted': if self.options.mode != 'inverted':
boss_map.update({ boss_map.update({
"Ganons Tower Basement": "Ganons Tower Basement":
self.dungeons["Ganons Tower"].bosses["bottom"].name, self.dungeons["Ganons Tower"].bosses["bottom"].name,
@@ -847,7 +836,7 @@ class ALTTPWorld(World):
"triforce_pieces_available", "triforce_pieces_extra", "triforce_pieces_available", "triforce_pieces_extra",
] ]
slot_data = {option_name: getattr(self.multiworld, option_name)[self.player].value for option_name in slot_options} slot_data = {option_name: getattr(self.options, option_name).value for option_name in slot_options}
slot_data.update({ slot_data.update({
'mm_medalion': self.required_medallions[0], 'mm_medalion': self.required_medallions[0],
@@ -868,8 +857,8 @@ def get_same_seed(world, seed_def: tuple) -> str:
class ALttPLogic(LogicMixin): class ALttPLogic(LogicMixin):
def _lttp_has_key(self, item, player, count: int = 1): def _lttp_has_key(self, item, player, count: int = 1):
if self.multiworld.glitches_required[player] == 'no_logic': if self.multiworld.worlds[player].options.glitches_required == 'no_logic':
return True return True
if self.multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: if self.multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal:
return can_buy_unlimited(self, 'Small Key (Universal)', player) return can_buy_unlimited(self, 'Small Key (Universal)', player)
return self.prog_items[player][item] >= count return self.prog_items[player][item] >= count

View File

@@ -14,8 +14,8 @@ class TestDungeon(LTTPTestBase):
self.starting_regions = [] # Where to start exploring self.starting_regions = [] # Where to start exploring
self.remove_exits = [] # Block dungeon exits self.remove_exits = [] # Block dungeon exits
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
self.multiworld.bombless_start[1].value = True self.multiworld.worlds[1].options.bombless_start.value = True
self.multiworld.shuffle_capacity_upgrades[1].value = 2 self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2
create_regions(self.multiworld, 1) create_regions(self.multiworld, 1)
self.multiworld.worlds[1].create_dungeons() self.multiworld.worlds[1].create_dungeons()
create_shops(self.multiworld, 1) create_shops(self.multiworld, 1)

View File

@@ -14,9 +14,9 @@ class TestInverted(TestBase, LTTPTestBase):
def setUp(self): def setUp(self):
self.world_setup() self.world_setup()
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
self.multiworld.mode[1].value = 2 self.multiworld.worlds[1].options.mode.value = 2
self.multiworld.bombless_start[1].value = True self.multiworld.worlds[1].options.bombless_start.value = True
self.multiworld.shuffle_capacity_upgrades[1].value = 2 self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2
create_inverted_regions(self.multiworld, 1) create_inverted_regions(self.multiworld, 1)
self.world.create_dungeons() self.world.create_dungeons()
create_shops(self.multiworld, 1) create_shops(self.multiworld, 1)

View File

@@ -12,7 +12,7 @@ class TestInvertedBombRules(LTTPTestBase):
def setUp(self): def setUp(self):
self.world_setup() self.world_setup()
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
self.multiworld.mode[1].value = 2 self.multiworld.worlds[1].options.mode.value = 2
create_inverted_regions(self.multiworld, 1) create_inverted_regions(self.multiworld, 1)
self.multiworld.worlds[1].create_dungeons() self.multiworld.worlds[1].create_dungeons()

View File

@@ -14,10 +14,10 @@ from worlds.alttp.test import LTTPTestBase
class TestInvertedMinor(TestBase, LTTPTestBase): class TestInvertedMinor(TestBase, LTTPTestBase):
def setUp(self): def setUp(self):
self.world_setup() self.world_setup()
self.multiworld.mode[1].value = 2 self.multiworld.worlds[1].options.mode.value = 2
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("minor_glitches") self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("minor_glitches")
self.multiworld.bombless_start[1].value = True self.multiworld.worlds[1].options.bombless_start.value = True
self.multiworld.shuffle_capacity_upgrades[1].value = 2 self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
create_inverted_regions(self.multiworld, 1) create_inverted_regions(self.multiworld, 1)
self.world.create_dungeons() self.world.create_dungeons()

View File

@@ -14,10 +14,10 @@ from worlds.alttp.test import LTTPTestBase
class TestInvertedOWG(TestBase, LTTPTestBase): class TestInvertedOWG(TestBase, LTTPTestBase):
def setUp(self): def setUp(self):
self.world_setup() self.world_setup()
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("overworld_glitches") self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("overworld_glitches")
self.multiworld.mode[1].value = 2 self.multiworld.worlds[1].options.mode.value = 2
self.multiworld.bombless_start[1].value = True self.multiworld.worlds[1].options.bombless_start.value = True
self.multiworld.shuffle_capacity_upgrades[1].value = 2 self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
create_inverted_regions(self.multiworld, 1) create_inverted_regions(self.multiworld, 1)
self.world.create_dungeons() self.world.create_dungeons()

View File

@@ -11,9 +11,9 @@ from worlds.alttp.test import LTTPTestBase
class TestMinor(TestBase, LTTPTestBase): class TestMinor(TestBase, LTTPTestBase):
def setUp(self): def setUp(self):
self.world_setup() self.world_setup()
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("minor_glitches") self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("minor_glitches")
self.multiworld.bombless_start[1].value = True self.multiworld.worlds[1].options.bombless_start.value = True
self.multiworld.shuffle_capacity_upgrades[1].value = 2 self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
self.world.er_seed = 0 self.world.er_seed = 0
self.world.create_regions() self.world.create_regions()

View File

@@ -23,7 +23,7 @@ class GoalPyramidTest(PyramidTestBase):
} }
def testCrystalsGoalAccess(self): def testCrystalsGoalAccess(self):
self.multiworld.goal[1].value = 1 # crystals self.multiworld.worlds[1].options.goal.value = 1 # crystals
self.assertFalse(self.can_reach_entrance("Pyramid Hole")) self.assertFalse(self.can_reach_entrance("Pyramid Hole"))
self.collect_by_name(["Hammer", "Progressive Glove", "Moon Pearl"]) self.collect_by_name(["Hammer", "Progressive Glove", "Moon Pearl"])
self.assertTrue(self.can_reach_entrance("Pyramid Hole")) self.assertTrue(self.can_reach_entrance("Pyramid Hole"))

View File

@@ -12,9 +12,9 @@ class TestVanillaOWG(TestBase, LTTPTestBase):
def setUp(self): def setUp(self):
self.world_setup() self.world_setup()
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("overworld_glitches") self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("overworld_glitches")
self.multiworld.bombless_start[1].value = True self.multiworld.worlds[1].options.bombless_start.value = True
self.multiworld.shuffle_capacity_upgrades[1].value = 2 self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2
self.multiworld.worlds[1].er_seed = 0 self.multiworld.worlds[1].er_seed = 0
self.multiworld.worlds[1].create_regions() self.multiworld.worlds[1].create_regions()
self.multiworld.worlds[1].create_items() self.multiworld.worlds[1].create_items()

View File

@@ -10,10 +10,10 @@ from worlds.alttp.test import LTTPTestBase
class TestVanilla(TestBase, LTTPTestBase): class TestVanilla(TestBase, LTTPTestBase):
def setUp(self): def setUp(self):
self.world_setup() self.world_setup()
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("no_glitches") self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("no_glitches")
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
self.multiworld.bombless_start[1].value = True self.multiworld.worlds[1].options.bombless_start.value = True
self.multiworld.shuffle_capacity_upgrades[1].value = 2 self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2
self.multiworld.worlds[1].er_seed = 0 self.multiworld.worlds[1].er_seed = 0
self.multiworld.worlds[1].create_regions() self.multiworld.worlds[1].create_regions()
self.multiworld.worlds[1].create_items() self.multiworld.worlds[1].create_items()