From ab28858a8f0d390a53b273f67f26a1ea23e287a4 Mon Sep 17 00:00:00 2001 From: Bonta-kun <40473493+Bonta0@users.noreply.github.com> Date: Mon, 16 Dec 2019 16:54:46 +0100 Subject: [PATCH] Individual settings: mode --- BaseClasses.py | 14 +++++++------- Bosses.py | 2 +- Dungeons.py | 2 +- EntranceRandomizer.py | 1 + EntranceShuffle.py | 42 ++++++++++++++++++++--------------------- Fill.py | 4 ++-- InvertedRegions.py | 6 +++--- ItemList.py | 12 ++++++------ Main.py | 44 +++++++++++++++++++------------------------ Plando.py | 6 +++--- Regions.py | 6 +++--- Rom.py | 44 +++++++++++++++++++++---------------------- Rules.py | 24 +++++++++++------------ 13 files changed, 101 insertions(+), 106 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 752a3817..11a94f2b 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -12,7 +12,7 @@ class World(object): self.players = players self.shuffle = shuffle self.logic = logic.copy() - self.mode = mode + self.mode = mode.copy() self.swords = swords self.difficulty = difficulty self.difficulty_adjustments = difficulty_adjustments @@ -39,7 +39,7 @@ class World(object): self.powder_patch_required = {player: False for player in range(1, players + 1)} self.ganon_at_pyramid = {player: True for player in range(1, players + 1)} self.ganonstower_vanilla = {player: True for player in range(1, players + 1)} - self.sewer_light_cone = mode == 'standard' + self.sewer_light_cone = {player: mode[player] == 'standard' for player in range(1, players + 1)} self.light_world_light_cone = False self.dark_world_light_cone = False self.treasure_hunt_count = 0 @@ -48,7 +48,7 @@ class World(object): self.rupoor_cost = 10 self.aga_randomness = True self.lock_aga_door_in_escape = False - self.fix_trock_doors = self.shuffle != 'vanilla' or self.mode == 'inverted' + self.fix_trock_doors = {player: self.shuffle != 'vanilla' or self.mode[player] == 'inverted' for player in range(1, players + 1)} self.save_and_quit_from_boss = True self.accessibility = accessibility self.fix_skullwoods_exit = self.shuffle not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'] @@ -74,7 +74,7 @@ class World(object): self.difficulty_requirements = None self.fix_fake_world = True self.boss_shuffle = boss_shuffle - self.escape_assist = [] + self.escape_assist = {player: [] for player in range(1, players + 1)} self.hints = hints self.crystals_needed_for_ganon = 7 self.crystals_needed_for_gt = 7 @@ -499,7 +499,7 @@ class CollectionState(object): if self.has_Pearl(player): return True - return region.is_light_world if self.world.mode != 'inverted' else region.is_dark_world + return region.is_light_world if self.world.mode[player] != 'inverted' else region.is_dark_world def can_reach_light_world(self, player): if True in [i.is_light_world for i in self.reachable_regions[player]]: @@ -689,7 +689,7 @@ class Region(object): or (item.bigkey and not self.world.bigkeyshuffle) or (item.map and not self.world.mapshuffle) or (item.compass and not self.world.compassshuffle)) - sewer_hack = self.world.mode == 'standard' and item.name == 'Small Key (Escape)' + sewer_hack = self.world.mode[item.player] == 'standard' and item.name == 'Small Key (Escape)' if sewer_hack or inside_dungeon_item: return self.dungeon and self.dungeon.is_dungeon_item(item) and item.player == self.player @@ -1025,7 +1025,7 @@ class Spoiler(object): self.bosses[str(player)]["Ice Palace"] = self.world.get_dungeon("Ice Palace", player).boss.name self.bosses[str(player)]["Misery Mire"] = self.world.get_dungeon("Misery Mire", player).boss.name self.bosses[str(player)]["Turtle Rock"] = self.world.get_dungeon("Turtle Rock", player).boss.name - if self.world.mode != 'inverted': + if self.world.mode[player] != 'inverted': self.bosses[str(player)]["Ganons Tower Basement"] = self.world.get_dungeon('Ganons Tower', player).bosses['bottom'].name self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Ganons Tower', player).bosses['middle'].name self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Ganons Tower', player).bosses['top'].name diff --git a/Bosses.py b/Bosses.py index 2713c92c..a4b9b313 100644 --- a/Bosses.py +++ b/Bosses.py @@ -141,7 +141,7 @@ def place_bosses(world, player): if world.boss_shuffle == 'none': return # Most to least restrictive order - if world.mode != 'inverted': + if world.mode[player] != 'inverted': boss_locations = [ ['Ganons Tower', 'top'], ['Tower of Hera', None], diff --git a/Dungeons.py b/Dungeons.py index 65a585a7..a176af13 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -27,7 +27,7 @@ def create_dungeons(world, player): MM = make_dungeon('Misery Mire', 'Vitreous', ['Misery Mire (Entrance)', 'Misery Mire (Main)', 'Misery Mire (West)', 'Misery Mire (Final Area)', 'Misery Mire (Vitreous)'], ItemFactory('Big Key (Misery Mire)', player), ItemFactory(['Small Key (Misery Mire)'] * 3, player), ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player)) TR = make_dungeon('Turtle Rock', 'Trinexx', ['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)', 'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'], ItemFactory('Big Key (Turtle Rock)', player), ItemFactory(['Small Key (Turtle Rock)'] * 4, player), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player)) - if world.mode != 'inverted': + if world.mode[player] != 'inverted': AT = make_dungeon('Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), []) GT = make_dungeon('Ganons Tower', 'Agahnim2', ['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)', player), ItemFactory(['Small Key (Ganons Tower)'] * 4, player), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player)) else: diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index b8584d54..bba20536 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -286,6 +286,7 @@ def parse_arguments(argv, no_defaults=False): getattr(ret, name)[player] = value set_player_arg("logic") + set_player_arg("mode") return ret diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 1cebd6f9..f20765a0 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -39,7 +39,7 @@ def link_entrances(world, player): lw_entrances = list(LW_Dungeon_Entrances) dw_entrances = list(DW_Dungeon_Entrances) - if world.mode == 'standard': + if world.mode[player] == 'standard': # must connect front of hyrule castle to do escape connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) else: @@ -52,7 +52,7 @@ def link_entrances(world, player): dw_entrances.append('Ganons Tower') dungeon_exits.append('Ganons Tower Exit') - if world.mode == 'standard': + if world.mode[player] == 'standard': # rest of hyrule castle must be in light world, so it has to be the one connected to east exit of desert connect_mandatory_exits(world, lw_entrances, [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], list(LW_Dungeon_Entrances_Must_Exit), player) else: @@ -273,7 +273,7 @@ def link_entrances(world, player): # tavern back door cannot be shuffled yet connect_doors(world, ['Tavern North'], ['Tavern'], player) - if world.mode == 'standard': + if world.mode[player] == 'standard': # must connect front of hyrule castle to do escape connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) else: @@ -309,7 +309,7 @@ def link_entrances(world, player): pass else: #if the cave wasn't placed we get here connect_caves(world, lw_entrances, [], old_man_house, player) - if world.mode == 'standard': + if world.mode[player] == 'standard': # rest of hyrule castle must be in light world connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) @@ -376,7 +376,7 @@ def link_entrances(world, player): # tavern back door cannot be shuffled yet connect_doors(world, ['Tavern North'], ['Tavern'], player) - if world.mode == 'standard': + if world.mode[player] == 'standard': # must connect front of hyrule castle to do escape connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) else: @@ -392,7 +392,7 @@ def link_entrances(world, player): #place must-exit caves connect_mandatory_exits(world, entrances, caves, must_exits, player) - if world.mode == 'standard': + if world.mode[player] == 'standard': # rest of hyrule castle must be dealt with connect_caves(world, entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) @@ -451,7 +451,7 @@ def link_entrances(world, player): blacksmith_doors = list(Blacksmith_Single_Cave_Doors) door_targets = list(Single_Cave_Targets) - if world.mode == 'standard': + if world.mode[player] == 'standard': # must connect front of hyrule castle to do escape connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) else: @@ -471,7 +471,7 @@ def link_entrances(world, player): else: connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) - if world.mode == 'standard': + if world.mode[player] == 'standard': # rest of hyrule castle must be in light world connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) @@ -552,7 +552,7 @@ def link_entrances(world, player): ('Lumberjack Tree Exit', 'Lumberjack Tree (top)'), (('Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'), 'Skull Woods Second Section (Drop)')] - if world.mode == 'standard': + if world.mode[player] == 'standard': # cannot move uncle cave 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) @@ -606,7 +606,7 @@ def link_entrances(world, player): connect_entrance(world, hole, target, player) # hyrule castle handling - if world.mode == 'standard': + if world.mode[player] == 'standard': # must connect front of hyrule castle to do escape connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player) @@ -792,7 +792,7 @@ def link_entrances(world, player): # tavern back door cannot be shuffled yet connect_doors(world, ['Tavern North'], ['Tavern'], player) - if world.mode == 'standard': + if world.mode[player] == 'standard': # cannot move uncle cave 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) @@ -825,7 +825,7 @@ def link_entrances(world, player): connect_entrance(world, hole, hole_targets.pop(), player) # hyrule castle handling - if world.mode == 'standard': + if world.mode[player] == 'standard': # must connect front of hyrule castle to do escape connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player) @@ -927,7 +927,7 @@ def link_entrances(world, player): hole_targets = ['Kakariko Well (top)', 'Bat Cave (right)', 'North Fairy Cave', 'Lost Woods Hideout (top)', 'Lumberjack Tree (top)', 'Sewer Drop', 'Skull Woods Second Section (Drop)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Top)'] - if world.mode == 'standard': + if world.mode[player] == 'standard': # cannot move uncle cave 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) @@ -960,7 +960,7 @@ def link_entrances(world, player): connect_entrance(world, hole, hole_targets.pop(), player) # hyrule castle handling - if world.mode == 'standard': + if world.mode[player] == 'standard': # must connect front of hyrule castle to do escape connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player) @@ -1831,7 +1831,7 @@ def scramble_holes(world, player): else: hole_targets.append(('Pyramid Exit', 'Pyramid')) - if world.mode == 'standard': + if world.mode[player] == 'standard': # cannot move uncle cave 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) @@ -1931,11 +1931,11 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player, dp_m if len(cave) == 2: entrance = entrances.pop() # ToDo Better solution, this is a hot fix. Do not connect both sides of trock/desert ledge only to each other - if world.mode != 'inverted' and entrance == 'Dark Death Mountain Ledge (West)': + if world.mode[player] != 'inverted' and entrance == 'Dark Death Mountain Ledge (West)': new_entrance = entrances.pop() entrances.append(entrance) entrance = new_entrance - if world.mode == 'inverted' and entrance == dp_must_exit: + if world.mode[player] == 'inverted' and entrance == dp_must_exit: new_entrance = entrances.pop() entrances.append(entrance) entrance = new_entrance @@ -2006,7 +2006,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_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 != 'inverted': + if world.mode[player] != 'inverted': if not world.shuffle_ganon: connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) else: @@ -2021,13 +2021,13 @@ def simple_shuffle_dungeons(world, player): # mix up 4 door dungeons multi_dungeons = ['Desert', 'Turtle Rock'] - if world.mode == 'open' or (world.mode == 'inverted' and world.shuffle_ganon): + if world.mode[player] == 'open' or (world.mode[player] == 'inverted' and world.shuffle_ganon): multi_dungeons.append('Hyrule Castle') random.shuffle(multi_dungeons) dp_target = multi_dungeons[0] tr_target = multi_dungeons[1] - if world.mode not in ['open', 'inverted'] or (world.mode == 'inverted' and world.shuffle_ganon is False): + if world.mode[player] not in ['open', 'inverted'] or (world.mode[player] == 'inverted' and world.shuffle_ganon is False): # place hyrule castle as intended hc_target = 'Hyrule Castle' else: @@ -2035,7 +2035,7 @@ def simple_shuffle_dungeons(world, player): # ToDo improve this? - if world.mode != 'inverted': + if world.mode[player] != 'inverted': 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 (East)', 'Hyrule Castle Exit (East)', player) diff --git a/Fill.py b/Fill.py index c369da20..4594ba65 100644 --- a/Fill.py +++ b/Fill.py @@ -239,8 +239,8 @@ def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=No fill_locations.reverse() # Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots - if world.keyshuffle and world.mode == 'standard': - progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' else 0) + if world.keyshuffle: + progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.mode[item.player] == 'standard' else 0) fill_restrictive(world, world.state, fill_locations, progitempool) diff --git a/InvertedRegions.py b/InvertedRegions.py index 61c257ff..48234e02 100644 --- a/InvertedRegions.py +++ b/InvertedRegions.py @@ -344,10 +344,10 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None ret.locations.append(Location(player, location, address, crystal, hint_text, ret, player_address)) return ret -def mark_dark_world_regions(world): +def mark_dark_world_regions(world, player): # cross world caves may have some sections marked as both in_light_world, and in_dark_work. # That is ok. the bunny logic will check for this case and incorporate special rules. - queue = collections.deque(region for region in world.regions if region.type == RegionType.DarkWorld) + queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.DarkWorld) seen = set(queue) while queue: current = queue.popleft() @@ -360,7 +360,7 @@ def mark_dark_world_regions(world): seen.add(exit.connected_region) queue.append(exit.connected_region) - queue = collections.deque(region for region in world.regions if region.type == RegionType.LightWorld) + queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.LightWorld) seen = set(queue) while queue: current = queue.popleft() diff --git a/ItemList.py b/ItemList.py index dba4a38f..706a7c4a 100644 --- a/ItemList.py +++ b/ItemList.py @@ -126,7 +126,7 @@ difficulties = { def generate_itempool(world, player): if (world.difficulty not in ['normal', 'hard', 'expert'] or world.goal not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'] - or world.mode not in ['open', 'standard', 'inverted'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']): + or world.mode[player] not in ['open', 'standard', 'inverted'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']): raise NotImplementedError('Not supported yet') if world.timer in ['ohko', 'timed-ohko']: @@ -138,7 +138,7 @@ def generate_itempool(world, player): world.push_item(world.get_location('Ganon', player), ItemFactory('Triforce', player), False) if world.goal in ['triforcehunt']: - if world.mode == 'inverted': + if world.mode[player] == 'inverted': region = world.get_region('Light World',player) else: region = world.get_region('Hyrule Castle Courtyard', player) @@ -177,15 +177,15 @@ def generate_itempool(world, player): # set up item pool if world.custom: - (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.swords, world.retro, world.customitemarray) + (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode[player], world.swords, world.retro, world.customitemarray) world.rupoor_cost = min(world.customitemarray[69], 9999) else: - (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.swords, world.retro) + (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode[player], world.swords, world.retro) for item in precollected_items: world.push_precollected(ItemFactory(item, player)) - if world.mode == 'standard' and not world.state.has_blunt_weapon(player) and "Link's Uncle" not in placed_items: + if world.mode[player] == 'standard' and not world.state.has_blunt_weapon(player) and "Link's Uncle" not in placed_items: found_sword = False found_bow = False possible_weapons = [] @@ -261,7 +261,7 @@ take_any_locations = [ 'Dark Lake Hylia Ledge Spike Cave', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Desert Hint'] def set_up_take_anys(world, player): - if world.mode == 'inverted' and 'Dark Sanctuary Hint' in take_any_locations: + if world.mode[player] == 'inverted' and 'Dark Sanctuary Hint' in take_any_locations: take_any_locations.remove('Dark Sanctuary Hint') regions = random.sample(take_any_locations, 5) diff --git a/Main.py b/Main.py index 4c8d11a1..307ed889 100644 --- a/Main.py +++ b/Main.py @@ -56,30 +56,26 @@ def main(args, seed=None): logger.info('ALttP Entrance Randomizer Version %s - Seed: %s\n\n', __version__, world.seed) world.difficulty_requirements = difficulties[world.difficulty] - if world.mode == 'standard' and (args.shuffleenemies != 'none' or args.enemy_health not in ['default', 'easy']): - world.escape_assist.append(['bombs']) # enemized escape assumes infinite bombs available and will likely be unbeatable without it - if world.mode != 'inverted': - for player in range(1, world.players + 1): + for player in range(1, world.players + 1): + if world.mode[player] == 'standard' and (args.shuffleenemies != 'none' or args.enemy_health not in ['default', 'easy']): + world.escape_assist[player].append(['bombs']) # enemized escape assumes infinite bombs available and will likely be unbeatable without it + + if world.mode[player] != 'inverted': create_regions(world, player) - create_dungeons(world, player) - else: - for player in range(1, world.players + 1): + else: create_inverted_regions(world, player) - create_dungeons(world, player) + create_dungeons(world, player) logger.info('Shuffling the World about.') - if world.mode != 'inverted': - for player in range(1, world.players + 1): + for player in range(1, world.players + 1): + if world.mode[player] != 'inverted': link_entrances(world, player) - - mark_light_world_regions(world) - else: - for player in range(1, world.players + 1): + mark_light_world_regions(world, player) + else: link_inverted_entrances(world, player) - - mark_dark_world_regions(world) + mark_dark_world_regions(world, player) logger.info('Generating Item Pool.') @@ -189,7 +185,7 @@ def main(args, seed=None): outfilesuffix = ('%s%s_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s' % (f'_P{player}' if world.players > 1 else '', f'_{player_names[player]}' if player in player_names else '', world.logic[player], world.difficulty, world.difficulty_adjustments, - world.mode, world.goal, + world.mode[player], world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, mcsb_name, "-retro" if world.retro else "", @@ -232,7 +228,7 @@ def copy_world(world): ret.ganonstower_vanilla = world.ganonstower_vanilla.copy() ret.treasure_hunt_count = world.treasure_hunt_count ret.treasure_hunt_icon = world.treasure_hunt_icon - ret.sewer_light_cone = world.sewer_light_cone + ret.sewer_light_cone = world.sewer_light_cone.copy() ret.light_world_light_cone = world.light_world_light_cone ret.dark_world_light_cone = world.dark_world_light_cone ret.seed = world.seed @@ -251,14 +247,12 @@ def copy_world(world): ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon ret.crystals_needed_for_gt = world.crystals_needed_for_gt - if world.mode != 'inverted': - for player in range(1, world.players + 1): + for player in range(1, world.players + 1): + if world.mode[player] != 'inverted': create_regions(ret, player) - create_dungeons(ret, player) - else: - for player in range(1, world.players + 1): + else: create_inverted_regions(ret, player) - create_dungeons(ret, player) + create_dungeons(ret, player) copy_dynamic_regions_and_locations(world, ret) @@ -436,7 +430,7 @@ def create_playthrough(world): old_world.spoiler.paths.update({ str(location) : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player}) for _, path in dict(old_world.spoiler.paths).items(): if any(exit == 'Pyramid Fairy' for (_, exit) in path): - if world.mode != 'inverted': + if world.mode[player] != 'inverted': old_world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, world.get_region('Big Bomb Shop', player)) else: old_world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = get_path(state, world.get_region('Inverted Big Bomb Shop', player)) diff --git a/Plando.py b/Plando.py index eb331813..490da77d 100755 --- a/Plando.py +++ b/Plando.py @@ -116,7 +116,7 @@ def fill_world(world, plando, text_patches): tr_medallion = medallionstr.strip() elif line.startswith('!mode'): _, modestr = line.split(':', 1) - world.mode = modestr.strip() + world.mode = {1: modestr.strip()} elif line.startswith('!logic'): _, logicstr = line.split(':', 1) world.logic = {1: logicstr.strip()} @@ -125,7 +125,7 @@ def fill_world(world, plando, text_patches): world.goal = goalstr.strip() elif line.startswith('!light_cone_sewers'): _, sewerstr = line.split(':', 1) - world.sewer_light_cone = sewerstr.strip().lower() == 'true' + world.sewer_light_cone = {1: sewerstr.strip().lower() == 'true'} elif line.startswith('!light_cone_lw'): _, lwconestr = line.split(':', 1) world.light_world_light_cone = lwconestr.strip().lower() == 'true' @@ -134,7 +134,7 @@ def fill_world(world, plando, text_patches): world.dark_world_light_cone = dwconestr.strip().lower() == 'true' elif line.startswith('!fix_trock_doors'): _, trdstr = line.split(':', 1) - world.fix_trock_doors = trdstr.strip().lower() == 'true' + world.fix_trock_doors = {1: trdstr.strip().lower() == 'true'} elif line.startswith('!fix_trock_exit'): _, trfstr = line.split(':', 1) world.fix_trock_exit = trfstr.strip().lower() == 'true' diff --git a/Regions.py b/Regions.py index b74d399b..021cf217 100644 --- a/Regions.py +++ b/Regions.py @@ -335,10 +335,10 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None ret.locations.append(Location(player, location, address, crystal, hint_text, ret, player_address)) return ret -def mark_light_world_regions(world): +def mark_light_world_regions(world, player): # cross world caves may have some sections marked as both in_light_world, and in_dark_work. # That is ok. the bunny logic will check for this case and incorporate special rules. - queue = collections.deque(region for region in world.regions if region.type == RegionType.LightWorld) + queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.LightWorld) seen = set(queue) while queue: current = queue.popleft() @@ -351,7 +351,7 @@ def mark_light_world_regions(world): seen.add(exit.connected_region) queue.append(exit.connected_region) - queue = collections.deque(region for region in world.regions if region.type == RegionType.DarkWorld) + queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.DarkWorld) seen = set(queue) while queue: current = queue.popleft() diff --git a/Rom.py b/Rom.py index 649666fd..44642afc 100644 --- a/Rom.py +++ b/Rom.py @@ -248,7 +248,7 @@ def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shuffleene } } - if world.mode != 'inverted': + if world.mode[player] != 'inverted': options['ManualBosses']['GanonsTower1'] = world.get_dungeon('Ganons Tower', player).bosses['bottom'].enemizer_name options['ManualBosses']['GanonsTower2'] = world.get_dungeon('Ganons Tower', player).bosses['middle'].enemizer_name options['ManualBosses']['GanonsTower3'] = world.get_dungeon('Ganons Tower', player).bosses['top'].enemizer_name @@ -550,7 +550,7 @@ def patch_rom(world, player, rom, enemized): else: # patch door table rom.write_byte(0xDBB73 + exit.addresses, exit.target) - if world.mode == 'inverted': + if world.mode[player] == 'inverted': patch_shuffled_dark_sanc(world, rom, player) write_custom_shops(rom, world, player) @@ -578,11 +578,11 @@ def patch_rom(world, player, rom, enemized): rom.write_byte(0x51DE, 0x00) # set open mode: - if world.mode in ['open', 'inverted']: + if world.mode[player] in ['open', 'inverted']: rom.write_byte(0x180032, 0x01) # open mode - if world.mode == 'inverted': + if world.mode[player] == 'inverted': set_inverted_mode(world, rom) - elif world.mode == 'standard': + elif world.mode[player] == 'standard': rom.write_byte(0x180032, 0x00) # standard mode uncle_location = world.get_location('Link\'s Uncle', player) @@ -600,7 +600,7 @@ def patch_rom(world, player, rom, enemized): rom.write_bytes(0x6D323, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E]) # set light cones - rom.write_byte(0x180038, 0x01 if world.sewer_light_cone else 0x00) + rom.write_byte(0x180038, 0x01 if world.sewer_light_cone[player] 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) @@ -892,7 +892,7 @@ def patch_rom(world, player, rom, enemized): # assorted fixes rom.write_byte(0x1800A2, 0x01) # remain in real dark world when dying in dark world dungeon before killing aga1 rom.write_byte(0x180169, 0x01 if world.lock_aga_door_in_escape else 0x00) # Lock or unlock aga tower door during escape sequence. - if world.mode == 'inverted': + if world.mode[player] == 'inverted': rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted rom.write_byte(0x180171, 0x01 if world.ganon_at_pyramid[player] else 0x00) # Enable respawning on pyramid after ganon death rom.write_byte(0x180173, 0x01) # Bob is enabled @@ -930,18 +930,18 @@ def patch_rom(world, player, rom, enemized): else: raise RuntimeError("Unsupported pre-collected item: {}".format(item)) - rom.write_byte(0x18004A, 0x00 if world.mode != 'inverted' else 0x01) # Inverted mode + rom.write_byte(0x18004A, 0x00 if world.mode[player] != 'inverted' else 0x01) # Inverted mode rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier - rom.write_byte(0x2AF79, 0xD0 if world.mode != 'inverted' else 0xF0) # vortexes: Normal (D0=light to dark, F0=dark to light, 42 = both) - rom.write_byte(0x3A943, 0xD0 if world.mode != 'inverted' else 0xF0) # Mirror: Normal (D0=Dark to Light, F0=light to dark, 42 = both) - rom.write_byte(0x3A96D, 0xF0 if world.mode != 'inverted' else 0xD0) # Residual Portal: Normal (F0= Light Side, D0=Dark Side, 42 = both (Darth Vader)) + rom.write_byte(0x2AF79, 0xD0 if world.mode[player] != 'inverted' else 0xF0) # vortexes: Normal (D0=light to dark, F0=dark to light, 42 = both) + rom.write_byte(0x3A943, 0xD0 if world.mode[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_bytes(0x180080, [50, 50, 70, 70]) # values to fill for Capacity Upgrades (Bomb5, Bomb10, Arrow5, Arrow10) - rom.write_byte(0x18004D, ((0x01 if 'arrows' in world.escape_assist else 0x00) | - (0x02 if 'bombs' in world.escape_assist else 0x00) | - (0x04 if 'magic' in world.escape_assist else 0x00))) # Escape assist + rom.write_byte(0x18004D, ((0x01 if 'arrows' in world.escape_assist[player] else 0x00) | + (0x02 if 'bombs' in world.escape_assist[player] else 0x00) | + (0x04 if 'magic' in world.escape_assist[player] else 0x00))) # Escape assist if world.goal in ['pedestal', 'triforcehunt']: rom.write_byte(0x18003E, 0x01) # make ganon invincible @@ -954,7 +954,7 @@ def patch_rom(world, player, rom, enemized): rom.write_byte(0x18005E, world.crystals_needed_for_gt) rom.write_byte(0x18005F, world.crystals_needed_for_ganon) - rom.write_byte(0x18008A, 0x01 if world.mode == "standard" else 0x00) # block HC upstairs doors in rain state in standard mode + rom.write_byte(0x18008A, 0x01 if world.mode[player] == "standard" else 0x00) # block HC upstairs doors in rain state in standard mode rom.write_byte(0x18016A, 0x10 | ((0x01 if world.keyshuffle else 0x00) | (0x02 if world.compassshuffle else 0x00) @@ -1031,7 +1031,7 @@ def patch_rom(world, player, rom, enemized): 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(0x18018B, [0,0,0]) # Mantle respawn refills (magic, bombs, arrows) - if world.mode == 'standard': + if world.mode[player] == 'standard': if uncle_location.item is not None and uncle_location.item.name in ['Bow', 'Progressive Bow']: rom.write_byte(0x18004E, 1) # Escape Fill (arrows) write_int16(rom, 0x180183, 300) # Escape fill rupee bow @@ -1068,7 +1068,7 @@ def patch_rom(world, player, rom, enemized): rom.write_byte(0x4E3BB, 0xEB) # fix trock doors for reverse entrances - if world.fix_trock_doors: + if world.fix_trock_doors[player]: rom.write_byte(0xFED31, 0x0E) # preopen bombable exit rom.write_byte(0xFEE41, 0x0E) # preopen bombable exit # included unconditionally in base2current @@ -1375,7 +1375,7 @@ def write_strings(rom, world, player): entrances_to_hint = {} entrances_to_hint.update(InconvenientDungeonEntrances) if world.shuffle_ganon: - if world.mode == 'inverted': + if world.mode[player] == 'inverted': entrances_to_hint.update({'Inverted Ganons Tower': 'The sealed castle door'}) else: entrances_to_hint.update({'Ganons Tower': 'Ganon\'s Tower'}) @@ -1408,14 +1408,14 @@ def write_strings(rom, world, player): if world.shuffle not in ['simple', 'restricted', 'restricted_legacy']: entrances_to_hint.update(ConnectorEntrances) entrances_to_hint.update(DungeonEntrances) - if world.mode == 'inverted': + if world.mode[player] == 'inverted': entrances_to_hint.update({'Inverted Agahnims Tower': 'The dark mountain tower'}) else: entrances_to_hint.update({'Agahnims Tower': 'The sealed castle door'}) elif world.shuffle == 'restricted': entrances_to_hint.update(ConnectorEntrances) entrances_to_hint.update(OtherEntrances) - if world.mode == 'inverted': + if world.mode[player] == 'inverted': 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 Links House': 'The old hero\'s light home'}) @@ -1425,7 +1425,7 @@ def write_strings(rom, world, player): if world.shuffle in ['insanity', 'madness_legacy', 'insanity_legacy']: entrances_to_hint.update(InsanityEntrances) if world.shuffle_ganon: - if world.mode == 'inverted': + if world.mode[player] == 'inverted': entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'}) else: entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'}) @@ -1592,7 +1592,7 @@ def write_strings(rom, world, player): tt['tablet_bombos_book'] = bombos_text # inverted spawn menu changes - if world.mode == 'inverted': + if world.mode[player] == 'inverted': 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['intro_main'] = CompressedTextMapper.convert( diff --git a/Rules.py b/Rules.py index 7606872b..2e42403d 100644 --- a/Rules.py +++ b/Rules.py @@ -7,7 +7,7 @@ def set_rules(world, player): if world.logic[player] == 'nologic': logging.getLogger('').info('WARNING! Seeds generated under this logic often require major glitches and may be impossible!') - if world.mode != 'inverted': + if world.mode[player] != 'inverted': world.get_region('Links House', player).can_reach_private = lambda state: True world.get_region('Sanctuary', player).can_reach_private = lambda state: True old_rule = world.get_region('Old Man House', player).can_reach @@ -22,14 +22,14 @@ def set_rules(world, player): return global_rules(world, player) - if world.mode != 'inverted': + if world.mode[player] != 'inverted': default_rules(world, player) - if world.mode == 'open': + if world.mode[player] == 'open': open_rules(world, player) - elif world.mode == 'standard': + elif world.mode[player] == 'standard': standard_rules(world, player) - elif world.mode == 'inverted': + elif world.mode[player] == 'inverted': open_rules(world, player) inverted_rules(world, player) else: @@ -49,7 +49,7 @@ def set_rules(world, player): # require aga2 to beat ganon add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player)) - if world.mode != 'inverted': + if world.mode[player] != 'inverted': set_big_bomb_rules(world, player) else: set_inverted_big_bomb_rules(world, player) @@ -58,7 +58,7 @@ def set_rules(world, player): if not world.swamp_patch_required[player]: add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has_Mirror(player)) - if world.mode != 'inverted': + if world.mode[player] != 'inverted': set_bunny_rules(world, player) else: set_inverted_bunny_rules(world, player) @@ -345,7 +345,7 @@ def global_rules(world, player): def default_rules(world, player): - if world.mode == 'standard': + if world.mode[player] == 'standard': world.get_region('Hyrule Castle Secret Entrance', player).can_reach_private = lambda state: True old_rule = world.get_region('Links House', player).can_reach_private world.get_region('Links House', player).can_reach_private = lambda state: state.can_reach('Sanctuary', 'Region', player) or old_rule(state) @@ -621,7 +621,7 @@ def inverted_rules(world, player): set_rule(world.get_entrance('Inverted Ganons Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt, player)) def no_glitches_rules(world, player): - if world.mode != 'inverted': + if world.mode[player] != 'inverted': set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Flippers', player) or state.can_lift_rocks(player)) set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Flippers', player)) # can be fake flippered to set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has('Flippers', player)) @@ -672,7 +672,7 @@ def no_glitches_rules(world, player): add_conditional_lamp('Palace of Darkness Maze Door', 'Palace of Darkness (Entrance)', 'Entrance') add_conditional_lamp('Palace of Darkness - Dark Basement - Left', 'Palace of Darkness (Entrance)', 'Location') add_conditional_lamp('Palace of Darkness - Dark Basement - Right', 'Palace of Darkness (Entrance)', 'Location') - if world.mode != 'inverted': + if world.mode[player] != 'inverted': add_conditional_lamp('Agahnim 1', 'Agahnims Tower', 'Entrance') add_conditional_lamp('Castle Tower - Dark Maze', 'Agahnims Tower', 'Location') else: @@ -688,7 +688,7 @@ def no_glitches_rules(world, player): add_conditional_lamp('Eastern Palace - Boss', 'Eastern Palace', 'Location') add_conditional_lamp('Eastern Palace - Prize', 'Eastern Palace', 'Location') - if not world.sewer_light_cone: + if not world.sewer_light_cone[player]: add_lamp_requirement(world.get_location('Sewers - Dark Cross', player), player) add_lamp_requirement(world.get_entrance('Sewers Back Door', player), player) add_lamp_requirement(world.get_entrance('Throne Room', player), player) @@ -709,7 +709,7 @@ def swordless_rules(world, player): set_rule(world.get_location('Ganon', player), lambda state: state.has('Hammer', player) and state.has_fire_source(player) and state.has('Silver Arrows', player) and state.can_shoot_arrows(player) and state.has_crystals(world.crystals_needed_for_ganon, player)) 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 != 'inverted': + if world.mode[player] != '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('Turtle Rock', player), lambda state: state.has_Pearl(player) and state.has_turtle_rock_medallion(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_Pearl(player) and state.has_misery_mire_medallion(player)) # sword not required to use medallion for opening in swordless (!)