diff --git a/BaseClasses.py b/BaseClasses.py index e8d31f28..812dd5e7 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -72,6 +72,8 @@ class World(object): self.shuffle_ganon = shuffle_ganon self.fix_gtower_exit = self.shuffle_ganon self.retro = retro.copy() + self.rupee_bow = retro.copy() + self.universal_keys = retro.copy() self.custom = custom self.customitemarray = customitemarray self.can_take_damage = True @@ -109,6 +111,11 @@ class World(object): if self.mode[player] == "retro": self.mode[player] = "open" self.retro[player] = True + self.rupee_bow[player] = True + self.universal_keys[player] = True + if self.goal[player] == "z1": + self.rupee_bow[player] = True + self.universal_keys[player] = True def set_player_attr(attr, val): self.__dict__.setdefault(attr, {})[player] = val set_player_attr('_region_cache', {}) @@ -332,7 +339,7 @@ class World(object): else: if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']: return False - elif self.goal[player] in ['crystals', 'trinity']: + elif self.goal[player] in ['crystals', 'trinity', 'z1']: return True else: return False @@ -694,7 +701,7 @@ class CollectionState(object): if key_logic.sm_doors[door]: self.reached_doors[player].add(key_logic.sm_doors[door].name) if not connection.can_reach(self): - checklist_key = 'Universal' if self.world.retro[player] else dungeon_name + checklist_key = 'Universal' if self.world.universal_keys[player] else dungeon_name checklist = self.dungeons_to_check[player][checklist_key] checklist[connection.name] = (connection, crystal_state) elif door.name not in self.opened_doors[player]: @@ -1117,7 +1124,7 @@ class CollectionState(object): return self.prog_items[item, player] >= count def has_sm_key(self, item, player, count=1): - if self.world.retro[player]: + if self.world.universal_keys[player]: if self.world.mode[player] == 'standard' and self.world.doorShuffle[player] == 'vanilla' and item == 'Small Key (Escape)': return True # Cannot access the shop until escape is finished. This is safe because the key is manually placed in make_custom_item_pool return self.can_buy_unlimited('Small Key (Universal)', player) @@ -1253,7 +1260,7 @@ class CollectionState(object): or self.has('Cane of Somaria', player)) def can_shoot_arrows(self, player): - if self.world.retro[player]: + if self.world.rupee_bow[player]: #todo: Non-progressive silvers grant wooden arrows, but progressive bows do not. Always require shop arrows to be safe return self.has('Bow', player) and (self.can_buy_unlimited('Single Arrow', player) or self.has('Single Arrow', player)) return self.has('Bow', player) @@ -3212,7 +3219,7 @@ class Spoiler(object): outfile.write('Retro:'.ljust(line_width) + '%s\n' % yn(self.metadata['retro'][player])) outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player]) outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player]) - if self.metadata['goal'][player] in ['triforcehunt', 'trinity']: + if self.metadata['goal'][player] in ['triforcehunt', 'trinity', 'z1']: outfile.write('Triforce Pieces Required:'.ljust(line_width) + '%s\n' % self.metadata['triforcegoal'][player]) outfile.write('Triforce Pieces Total:'.ljust(line_width) + '%s\n' % self.metadata['triforcepool'][player]) outfile.write('Crystals Required for GT:'.ljust(line_width) + '%s\n' % str(self.world.crystals_gt_orig[player])) @@ -3536,7 +3543,7 @@ world_mode = {"open": 0, "standard": 1, "inverted": 2} sword_mode = {"random": 0, "assured": 1, "swordless": 2, "swordless_hammer": 2, "vanilla": 3, "bombs": 4, "pseudo": 5, "assured_pseudo": 5, "byrna": 6, "somaria": 6, "cane": 6, "bees": 7} # byte 2: GGGD DFFH (goal, diff, item_func, hints) -goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "crystals": 4, "trinity": 5} +goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "crystals": 4, "trinity": 5, "z1": 6} diff_mode = {"normal": 0, "hard": 1, "expert": 2} func_mode = {"normal": 0, "hard": 1, "expert": 2} diff --git a/DoorShuffle.py b/DoorShuffle.py index 145ff35d..1d28e84c 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -428,7 +428,7 @@ def choose_portals(world, player): for dungeon, info in shuffled_info: outstanding_portals = list(dungeon_portals[dungeon]) hc_flag = std_flag and dungeon == 'Hyrule Castle' - rupee_bow_flag = hc_flag and world.retro[player] # rupee bow + rupee_bow_flag = hc_flag and world.rupee_bow[player] # rupee bow if hc_flag: sanc = world.get_portal('Sanctuary', player) sanc.destination = True @@ -1003,7 +1003,7 @@ def cross_dungeon(world, player): assign_cross_keys(dungeon_builders, world, player) all_dungeon_items_cnt = len(list(y for x in world.dungeons if x.player == player for y in x.all_items)) target_items = 34 - if world.retro[player]: + if world.universal_keys[player]: target_items += 1 if world.dropshuffle[player] else 0 # the hc big key else: target_items += 29 # small keys in chests @@ -1067,7 +1067,7 @@ def cross_dungeon(world, player): def assign_cross_keys(dungeon_builders, world, player): logging.getLogger('').info(world.fish.translate("cli", "cli", "shuffling.keydoors")) start = time.process_time() - if world.retro[player]: + if world.universal_keys[player]: remaining = 29 if world.dropshuffle[player]: remaining += 13 @@ -1146,7 +1146,7 @@ def assign_cross_keys(dungeon_builders, world, player): # Last Step: Adjust Small Key Dungeon Pool for name, builder in dungeon_builders.items(): reassign_key_doors(builder, world, player) - if not world.retro[player]: + if not world.universal_keys[player]: log_key_logic(builder.name, world.key_logic[player][builder.name]) actual_chest_keys = max(builder.key_doors_num - builder.key_drop_cnt, 0) dungeon = world.get_dungeon(name, player) diff --git a/DungeonGenerator.py b/DungeonGenerator.py index f0038f45..f5ff1c93 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -1305,7 +1305,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, sanc_builder = random.choice(lw_builders) assign_sector(sanc, sanc_builder, candidate_sectors, global_pole) - bow_sectors, retro_std_flag = {}, world.retro[player] and world.mode[player] == 'standard' + bow_sectors, retro_std_flag = {}, world.rupee_bow[player] and world.mode[player] == 'standard' free_location_sectors = {} crystal_switches = {} crystal_barriers = {} diff --git a/Dungeons.py b/Dungeons.py index 188cf59f..e07c5250 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -5,7 +5,7 @@ from Items import ItemFactory def create_dungeons(world, player): def make_dungeon(name, id, default_boss, dungeon_regions, big_key, small_keys, dungeon_items): - dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.retro[player] else small_keys, + dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.universal_keys[player] else small_keys, dungeon_items, player, id) dungeon.boss = BossFactory(default_boss, player) for region in dungeon.regions: diff --git a/Fill.py b/Fill.py index 25dc295e..1f4a1a50 100644 --- a/Fill.py +++ b/Fill.py @@ -160,7 +160,7 @@ def valid_key_placement(item, location, key_pool, world): if not valid_reserved_placement(item, location, world): return False if ((not item.smallkey and not item.bigkey) or item.player != location.player - or world.retro[item.player] or world.logic[item.player] == 'nologic'): + or world.universal_keys[item.player] or world.logic[item.player] == 'nologic'): return True dungeon = location.parent_region.dungeon if dungeon: @@ -405,7 +405,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None else: max_trash = gt_count scaled_trash = math.floor(max_trash * scale_factor) - if world.goal[player] in ['triforcehunt', 'trinity']: + if world.goal[player] in ['triforcehunt', 'trinity', 'z1']: gftower_trash_count = random.randint(scaled_trash, max_trash) else: gftower_trash_count = random.randint(0, scaled_trash) @@ -507,7 +507,7 @@ def ensure_good_pots(world, write_skips=False): # else: # loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.player) # do the arrow retro check - if world.retro[loc.item.player] and loc.item.name in {'Arrows (5)', 'Arrows (10)'}: + if world.rupee_bow[loc.item.player] and loc.item.name in {'Arrows (5)', 'Arrows (10)'}: loc.item = ItemFactory('Rupees (5)', loc.item.player) # don't write out all pots to spoiler if write_skips: @@ -823,7 +823,9 @@ def balance_money_progression(world): return True if item.name in ['Progressive Armor', 'Blue Mail', 'Red Mail']: return True - if world.retro[player] and (item.name in ['Single Arrow', 'Small Key (Universal)']): + if world.universal_keys and item.name == 'Small Key (Universal)': + return True + if world.rupee_bow and item.name == 'Single Arrow': return True if location.name in pay_for_locations: return True @@ -975,7 +977,7 @@ def set_prize_drops(world, player): 0xE3: 0xD8} # Big magic -> small magic new_prizes = [prize_replacements.get(prize, prize) for prize in new_prizes] - if world.retro[player]: + if world.rupee_bow[player]: prize_replacements = {0xE1: 0xDA, #5 Arrows -> Blue Rupee 0xE2: 0xDB} #10 Arrows -> Red Rupee new_prizes = [prize_replacements.get(prize, prize) for prize in new_prizes] diff --git a/InitialSram.py b/InitialSram.py index 4af02457..18399ff3 100644 --- a/InitialSram.py +++ b/InitialSram.py @@ -76,7 +76,7 @@ class InitialSram: if startingstate.has('Bow', player): equip[0x340] = 3 if startingstate.has('Silver Arrows', player) else 1 equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases - if not world.retro[player]: + if not world.rupee_bow[player]: equip[0x38E] |= 0x80 if startingstate.has('Silver Arrows', player): equip[0x38E] |= 0x40 @@ -208,7 +208,7 @@ class InitialSram: elif item.name in bombs: starting_bombs += bombs[item.name] elif item.name in arrows: - if world.retro[player]: + if world.rupee_bow[player]: equip[0x38E] |= 0x80 starting_arrows = 1 else: diff --git a/ItemList.py b/ItemList.py index 8b18c686..5201d74b 100644 --- a/ItemList.py +++ b/ItemList.py @@ -39,7 +39,7 @@ Difficulty = namedtuple('Difficulty', ['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield', 'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'bombs_only', 'cane_only', 'progressivesword', 'basicsword', 'basicbow', 'timedohko', 'timedother', - 'retro', 'bombbag', + 'rupee_bow', 'bombbag', 'extras', 'progressive_sword_limit', 'progressive_shield_limit', 'progressive_armor_limit', 'progressive_bottle_limit', 'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit']) @@ -65,7 +65,7 @@ difficulties = { basicbow = ['Bow', 'Silver Arrows'], timedohko = ['Green Clock'] * 25, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, - retro = ['Small Key (Universal)'] * 18 + ['Rupees (20)'] * 10, + rupee_bow = ['Small Key (Universal)'] * 18 + ['Rupees (20)'] * 10, bombbag = ['Bomb Upgrade (+10)'] * 2, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 4, @@ -93,7 +93,7 @@ difficulties = { basicbow = ['Bow'] * 2, timedohko = ['Green Clock'] * 25, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, - retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15, + rupee_bow = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15, bombbag = ['Bomb Upgrade (+10)'] * 2, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 3, @@ -121,7 +121,7 @@ difficulties = { basicbow = ['Bow'] * 2, timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, - retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15, + rupee_bow = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15, bombbag = ['Bomb Upgrade (+10)'] * 2, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 2, @@ -188,7 +188,7 @@ def get_custom_array_key(item): def maybe_replace_item(world, player, item): - if world.retro[player] and item in ['Single Arrow', 'Arrows (5)', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)']: + if world.rupee_bow[player] and item in ['Single Arrow', 'Arrows (5)', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)']: return 'Rupees (5)' if world.swords[player] == 'bombs' and item in ['Single Bomb', 'Bombs (3)', 'Bombs (10)', 'Bomb Upgrade (+5)', 'Bomb Upgrade (+10)']: return 'Small Heart' @@ -196,7 +196,7 @@ def maybe_replace_item(world, player, item): def generate_itempool(world, player): - if (world.difficulty[player] not in ['normal', 'hard', 'expert'] or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'trinity', 'crystals'] + if (world.difficulty[player] not in ['normal', 'hard', 'expert'] or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'trinity', 'crystals', 'z1'] 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') @@ -346,7 +346,7 @@ def generate_itempool(world, player): if not found_sword and world.swords[player] != 'swordless': found_sword = True possible_weapons.append(item) - if item in ['Progressive Bow', 'Bow'] and not found_bow and not world.retro[player]: + if item in ['Progressive Bow', 'Bow'] and not found_bow and not world.rupee_bow[player]: found_bow = True possible_weapons.append(item) if item in ['Hammer', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']: @@ -396,7 +396,7 @@ def generate_itempool(world, player): world.clock_mode = clock_mode goal = world.goal[player] - if goal in ['triforcehunt', 'trinity']: + if goal in ['triforcehunt', 'trinity', 'z1']: g, t = set_default_triforce(goal, world.treasure_hunt_count[player], world.treasure_hunt_total[player]) world.treasure_hunt_count[player], world.treasure_hunt_total[player] = g, t world.treasure_hunt_icon[player] = 'Triforce Piece' @@ -448,6 +448,8 @@ def generate_itempool(world, player): if world.retro[player]: set_up_take_anys(world, player) + + if world.universal_keys[player]: if world.dropshuffle[player]: world.itempool += [ItemFactory('Small Key (Universal)', player)] * 13 if world.pottery[player] not in ['none', 'cave']: @@ -705,7 +707,8 @@ def fill_prizes(world, attempts=15): def set_up_shops(world, player): - if world.retro[player]: + # TODO: separate universal key replacements from rupee bow replacements + if world.retro[player] or world.goal[player] in ['z1']: if world.shopsanity[player]: removals = [next(item for item in world.itempool if item.name == 'Arrows (10)' and item.player == player)] red_pots = [item for item in world.itempool if item.name == 'Red Potion' and item.player == player][:5] @@ -765,7 +768,7 @@ def set_up_shops(world, player): def customize_shops(world, player): - found_bomb_upgrade, found_arrow_upgrade = False, world.retro[player] + found_bomb_upgrade, found_arrow_upgrade = False, world.rupee_bow[player] possible_replacements = [] shops_to_customize = shop_to_location_table.copy() if world.retro[player]: @@ -791,7 +794,7 @@ def customize_shops(world, player): price = 0 else: price = 120 if shop_name == 'Potion Shop' and item.name == 'Red Potion' else item.price - if world.retro[player] and item.name == 'Single Arrow': + if world.rupee_bow[player] and item.name == 'Single Arrow': price = 80 # randomize price shop.add_inventory(idx, item.name, randomize_price(price), max_repeat, player=item.player) @@ -818,7 +821,7 @@ def customize_shops(world, player): if not found_arrow_upgrade and len(possible_replacements) > 0: choices = [] for shop, idx, loc, item in possible_replacements: - if item.name == 'Arrows (10)' or (item.name == 'Single Arrow' and not world.retro[player]): + if item.name == 'Arrows (10)' or (item.name == 'Single Arrow' and not world.rupee_bow[player]): choices.append((shop, idx, loc, item)) if len(choices) > 0: shop, idx, loc, item = random.choice(choices) @@ -987,7 +990,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, placed_items = {} precollected_items = [] clock_mode = None - if treasure_hunt_total == 0 and goal in ['triforcehunt', 'trinity']: + if treasure_hunt_total == 0 and goal in ['triforcehunt', 'trinity', 'z1']: treasure_hunt_total = 30 if goal == 'triforcehunt' else 10 # triforce pieces max out triforcepool = ['Triforce Piece'] * min(treasure_hunt_total, max_goal) @@ -1005,6 +1008,23 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, def want_progressives(): return random.choice([True, False]) if progressive == 'random' else progressive == 'on' + # in z1 mode, preplace triforce pieces on bosses and boots on green pendant turn-in + if goal == 'z1': + place_item('Sahasrahla', 'Pegasus Boots') + place_item('Eastern Palace - Boss', 'Triforce Piece') + place_item('Desert Palace - Boss', 'Triforce Piece') + place_item('Tower of Hera - Boss', 'Triforce Piece') + place_item('Palace of Darkness - Boss', 'Triforce Piece') + place_item('Swamp Palace - Boss', 'Triforce Piece') + place_item('Skull Woods - Boss', 'Triforce Piece') + place_item('Thieves\' Town - Boss', 'Triforce Piece') + place_item('Ice Palace - Boss', 'Triforce Piece') + place_item('Misery Mire - Boss', 'Triforce Piece') + place_item('Turtle Rock - Boss', 'Triforce Piece') + pool.remove('Pegasus Boots') + pool.remove('Ocarina') + pool.append('Ocarina (Activated)') + # provide boots to boots glitch dependent modes if logic in ['owglitches', 'nologic']: precollected_items.append('Pegasus Boots') @@ -1033,6 +1053,13 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, diff = difficulties[difficulty] pool.extend(diff.baseitems) + if goal == 'z1': + pool.remove('Sanctuary Heart Container') + pool.extend(['Boss Heart Container'] * 3) + for _ in range(24): + pool.remove('Piece of Heart') + pool.extend(['Rupees (20)'] * 12) + if bombbag: pool = [item.replace('Bomb Upgrade (+5)','Rupees (5)') for item in pool] pool = [item.replace('Bomb Upgrade (+10)','Rupees (5)') for item in pool] @@ -1057,7 +1084,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, else: pool.extend(diff.basicarmor) - if want_progressives(): + if want_progressives() and goal != "z1": pool.extend(['Progressive Bow'] * 2) elif swords != 'swordless': pool.extend(diff.basicbow) @@ -1089,15 +1116,24 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, place_item('Master Sword Pedestal', 'Triforce') pool.append(swords_to_use.pop()) else: - pool.extend(diff.progressivesword if want_progressives() else diff.basicsword) - if swords in ['assured', 'assured_pseudo']: - if want_progressives(): + if want_progressives(): + pool.extend(diff.progressivesword) + if swords in ['assured', 'assured_pseudo']: precollected_items.append('Progressive Sword') pool.remove('Progressive Sword') - else: + pool.extend(['Rupees (50)']) + if goal in ['z1']: + pool.remove('Progressive Sword') + pool.extend(['Rupees (20)']) + else: + pool.extend(diff.basicsword) + if swords in ['assured', 'assured_pseudo']: precollected_items.append('Fighter Sword') pool.remove('Fighter Sword') - pool.extend(['Rupees (50)']) + pool.extend(['Rupees (50)']) + if goal in ['z1']: + pool.remove('Golden Sword') + pool.extend(['Rupees (20)']) if timer in ['timed', 'timed-countdown']: pool.extend(diff.timedother) @@ -1115,12 +1151,12 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, if goal in ['pedestal', 'trinity'] and swords != 'vanilla': place_item('Master Sword Pedestal', 'Triforce') - if retro: + if retro or goal == 'z1': pool = [item.replace('Single Arrow','Rupees (5)') for item in pool] pool = [item.replace('Arrows (10)','Rupees (5)') for item in pool] pool = [item.replace('Arrow Upgrade (+5)','Rupees (5)') for item in pool] pool = [item.replace('Arrow Upgrade (+10)','Rupees (5)') for item in pool] - pool.extend(diff.retro) + pool.extend(diff.rupee_bow) if door_shuffle != 'vanilla': # door shuffle needs more keys for retro replace = 'Rupees (20)' if difficulty == 'normal' else 'Rupees (5)' indices = [i for i, x in enumerate(pool) if x == replace] @@ -1168,7 +1204,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s customitemarray["triforce"] = total_items_to_place # Triforce Pieces - if goal in ['triforcehunt', 'trinity']: + if goal in ['triforcehunt', 'trinity', 'z1']: g, t = set_default_triforce(goal, customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"]) customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"] = g, t @@ -1206,7 +1242,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s treasure_hunt_count = max(min(customitemarray["triforcepiecesgoal"], max_goal), 1) treasure_hunt_icon = 'Triforce Piece' # Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling. - if ((customitemarray["triforcepieces"] < treasure_hunt_count) and (goal in ['triforcehunt', 'trinity']) + if ((customitemarray["triforcepieces"] < treasure_hunt_count) and (goal in ['triforcehunt', 'trinity', 'z1']) and (customitemarray["triforce"] == 0)): extrapieces = treasure_hunt_count - customitemarray["triforcepieces"] pool.extend(['Triforce Piece'] * extrapieces) @@ -1224,7 +1260,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s itemtotal = itemtotal + 1 if mode == 'standard': - if retro: + if retro or goal == 'z1': key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross']) place_item(key_location, 'Small Key (Universal)') pool.extend(['Small Key (Universal)'] * max((customitemarray["generickeys"] - 1), 0)) @@ -1236,6 +1272,23 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s pool.extend(['Fighter Sword'] * customitemarray["sword1"]) pool.extend(['Progressive Sword'] * customitemarray["progressivesword"]) + # in z1 mode, preplace triforce pieces on bosses and boots on green pendant turn-in + if goal == 'z1': + place_item('Sahasrahla', 'Pegasus Boots') + place_item('Eastern Palace - Boss', 'Triforce Piece') + place_item('Desert Palace - Boss', 'Triforce Piece') + place_item('Tower of Hera - Boss', 'Triforce Piece') + place_item('Palace of Darkness - Boss', 'Triforce Piece') + place_item('Swamp Palace - Boss', 'Triforce Piece') + place_item('Skull Woods - Boss', 'Triforce Piece') + place_item('Thieves\' Town - Boss', 'Triforce Piece') + place_item('Ice Palace - Boss', 'Triforce Piece') + place_item('Misery Mire - Boss', 'Triforce Piece') + place_item('Turtle Rock - Boss', 'Triforce Piece') + pool.remove('Pegasus Boots') + pool.remove('Ocarina') + pool.append('Ocarina (Activated)') + if shuffle == 'insanity_legacy': place_item('Link\'s House', 'Magic Mirror') place_item('Sanctuary', 'Moon Pearl') @@ -1245,7 +1298,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s pool.extend(['Magic Mirror'] * customitemarray["mirror"]) pool.extend(['Moon Pearl'] * customitemarray["pearl"]) - if retro: + if retro or goal == 'z1': itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in Retro Mode if itemtotal < total_items_to_place: nothings = total_items_to_place - itemtotal @@ -1254,19 +1307,6 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_total, treasure_hunt_icon, lamps_needed_for_dark_rooms) -def set_default_triforce(goal, custom_goal, custom_total): - triforce_goal, triforce_total = 0, 0 - if goal == 'triforcehunt': - triforce_goal, triforce_total = 20, 30 - elif goal == 'trinity': - triforce_goal, triforce_total = 8, 10 - if custom_goal > 0: - triforce_goal = max(min(custom_goal, 128), 1) - if custom_total > 0 or custom_goal > 0: - triforce_total = max(min(custom_total, 128), triforce_goal) #128 max to ensure other progression can fit. - return (triforce_goal, triforce_total) - - def make_customizer_pool(world, player): pool = [] placed_items = {} @@ -1307,17 +1347,22 @@ def set_default_triforce(goal, custom_goal, custom_total): triforce_goal, triforce_total = 20, 30 elif goal == 'trinity': triforce_goal, triforce_total = 8, 10 - if custom_goal > 0: - triforce_goal = max(min(custom_goal, max_goal), 1) - if custom_total > 0: - triforce_total = max(min(custom_total, max_goal), triforce_goal) + if goal == 'z1': + triforce_goal, triforce_total = 8, 10 + if custom_goal > 0: + triforce_goal = max(min(custom_goal, 10), 1) + else: + if custom_goal > 0: + triforce_goal = max(min(custom_goal, max_goal), 1) + if custom_total > 0: + triforce_total = max(min(custom_total, max_goal), triforce_goal) return triforce_goal, triforce_total # A quick test to ensure all combinations generate the correct amount of items. def test(): for difficulty in ['normal', 'hard', 'expert']: - for goal in ['ganon', 'triforcehunt', 'pedestal', 'trinity']: + for goal in ['ganon', 'triforcehunt', 'pedestal', 'trinity', 'z1']: for timer in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']: for mode in ['open', 'standard', 'inverted', 'retro']: for swords in ['random', 'assured', 'swordless', 'vanilla', 'bombs', 'byrna', 'somaria', 'cane']: diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index fc250852..00a58cf4 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -275,7 +275,7 @@ def analyze_dungeon(key_layout, world, player): key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations)) key_logic.bk_chests.update(find_big_key_locked_locations(key_layout.all_chest_locations)) key_logic.prize_location = dungeon_table[key_layout.sector.name].prize - if world.retro[player] and world.mode[player] != 'standard': + if world.universal_keys[player] and world.mode[player] != 'standard': return original_key_counter = find_counter({}, False, key_layout, False) @@ -926,7 +926,7 @@ def self_lock_possible(counter): def available_chest_small_keys(key_counter, world, player): - if not world.keyshuffle[player] and not world.retro[player]: + if not world.keyshuffle[player] and not world.universal_keys[player]: cnt = 0 for loc in key_counter.free_locations: if key_counter.big_key_opened or '- Big Chest' not in loc.name: @@ -937,7 +937,7 @@ def available_chest_small_keys(key_counter, world, player): def available_chest_small_keys_logic(key_counter, world, player, sm_restricted): - if not world.keyshuffle[player] and not world.retro[player]: + if not world.keyshuffle[player] and not world.universal_keys[player]: cnt = 0 for loc in key_counter.free_locations: if loc not in sm_restricted and (key_counter.big_key_opened or '- Big Chest' not in loc.name): @@ -1404,7 +1404,7 @@ def prize_relevance(key_layout, dungeon_entrance, is_atgt_swapped): # Soft lock stuff def validate_key_layout(key_layout, world, player): # retro is all good - except for hyrule castle in standard mode - if (world.retro[player] and (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle')) or world.logic[player] == 'nologic': + if (world.universal_keys[player] and (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle')) or world.logic[player] == 'nologic': return True flat_proposal = key_layout.flat_prop state = ExplorationState(dungeon=key_layout.sector.name) @@ -1532,7 +1532,7 @@ def enough_small_locations(state, avail_small_loc): def determine_prize_lock(key_layout, world, player): - if ((world.retro[player] and (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle')) + if ((world.universal_keys[player] and (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle')) or world.logic[player] == 'nologic'): return # done, doesn't matter what flat_proposal = key_layout.flat_prop @@ -1565,7 +1565,7 @@ def determine_prize_lock(key_layout, world, player): def cnt_avail_small_locations(free_locations, key_only, state, world, player): - if not world.keyshuffle[player] and not world.retro[player]: + if not world.keyshuffle[player] and not world.universal_keys[player]: bk_adj = 1 if state.big_key_opened and not state.big_key_special else 0 avail_chest_keys = min(free_locations - bk_adj, state.key_locations - key_only) return max(0, avail_chest_keys + key_only - state.used_smalls) @@ -1573,7 +1573,7 @@ def cnt_avail_small_locations(free_locations, key_only, state, world, player): def cnt_avail_small_locations_by_ctr(free_locations, counter, layout, world, player): - if not world.keyshuffle[player] and not world.retro[player]: + if not world.keyshuffle[player] and not world.universal_keys[player]: bk_adj = 1 if counter.big_key_opened and not layout.big_key_special else 0 avail_chest_keys = min(free_locations - bk_adj, layout.max_chests) return max(0, avail_chest_keys + len(counter.key_only_locations) - counter.used_keys) @@ -2016,7 +2016,7 @@ def val_rule(rule, skn, allow=False, loc=None, askn=None, setCheck=None): # Soft lock stuff def validate_key_placement(key_layout, world, player): - if world.retro[player] or world.accessibility[player] == 'none': + if world.universal_keys[player] or world.accessibility[player] == 'none': return True # Can't keylock in retro. Expected if beatable only. max_counter = find_max_counter(key_layout) keys_outside = 0 diff --git a/Main.py b/Main.py index ab87c2e1..c34f8450 100644 --- a/Main.py +++ b/Main.py @@ -251,7 +251,7 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): if world.shopsanity[player]: sell_potions(world, player) - if world.retro[player]: + if world.universal_keys[player]: sell_keys(world, player) else: lock_shop_locations(world, player) diff --git a/Mystery.py b/Mystery.py index 630631b7..b727e73f 100644 --- a/Mystery.py +++ b/Mystery.py @@ -212,7 +212,8 @@ def roll_settings(weights): 'dungeons': 'dungeons', 'pedestal': 'pedestal', 'triforce-hunt': 'triforcehunt', - 'trinity': 'trinity' + 'trinity': 'trinity', + 'z1': 'z1' }[goal] ret.openpyramid = get_choice('open_pyramid') if 'open_pyramid' in weights else 'auto' diff --git a/PotShuffle.py b/PotShuffle.py index cdb829f4..e4b4f50f 100644 --- a/PotShuffle.py +++ b/PotShuffle.py @@ -879,7 +879,7 @@ def shuffle_pots(world, player): elif old_pot.item == PotItem.Switch: available_pots = (pot for pot in new_pots if (pot.room == old_pot.room or pot.room in movable_switch_rooms[old_pot.room]) and not (pot.flags & PotFlags.NoSwitch)) elif old_pot.item == PotItem.Key: - if world.doorShuffle[player] == 'vanilla' and not world.retro[player] and world.pottery[player] == 'none' and world.logic[player] != 'nologic': + if world.doorShuffle[player] == 'vanilla' and not world.universal_keys[player] and world.pottery[player] == 'none' and world.logic[player] != 'nologic': available_pots = (pot for pot in new_pots if pot.room not in invalid_key_rooms) else: available_pots = new_pots @@ -890,7 +890,7 @@ def shuffle_pots(world, player): new_pot = random.choice(available_pots) new_pot.item = old_pot.item - if world.retro[player] and new_pot.item == PotItem.FiveArrows: + if world.rupee_bow[player] and new_pot.item == PotItem.FiveArrows: new_pot.item = PotItem.FiveRupees if new_pot.item == PotItem.Key: @@ -938,7 +938,7 @@ def shuffle_pot_switches(world, player): new_pot = random.choice(available_pots) new_pot.item, old_pot.item = old_pot.item, new_pot.item - if world.retro[player] and new_pot.item == PotItem.FiveArrows: + if world.rupee_bow[player] and new_pot.item == PotItem.FiveArrows: new_pot.item = PotItem.FiveRupees if new_pot.item == PotItem.Switch and (new_pot.flags & PotFlags.SwitchLogicChange): diff --git a/Regions.py b/Regions.py index 8f637e3a..8cec9018 100644 --- a/Regions.py +++ b/Regions.py @@ -1172,7 +1172,7 @@ def adjust_locations(world, player): loc.event = False item_dungeon = key_item.dungeon dungeon = world.get_dungeon(item_dungeon, player) - if key_item.smallkey and not world.retro[player]: + if key_item.smallkey and not world.universal_keys[player]: dungeon.small_keys.append(key_item) elif key_item.bigkey: dungeon.big_key = key_item diff --git a/Rom.py b/Rom.py index 7d4bbb6d..658ec023 100644 --- a/Rom.py +++ b/Rom.py @@ -882,7 +882,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal if world.doorShuffle[player] == 'crossed': dr_flags |= DROptions.Map_Info - if world.collection_rate[player] and world.goal[player] not in ['triforcehunt', 'trinity']: + if world.collection_rate[player] and world.goal[player] not in ['triforcehunt', 'trinity', 'z1']: dr_flags |= DROptions.Debug if world.doorShuffle[player] == 'crossed' and world.logic[player] != 'nologic'\ and world.mixed_travel[player] == 'prevent': @@ -948,7 +948,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x138002, 2) for name, layout in world.key_layout[player].items(): offset = compass_data[name][4]//2 - if world.retro[player]: + if world.universal_keys[player]: rom.write_byte(0x13f030+offset, layout.max_chests + layout.max_drops) else: rom.write_byte(0x13f020+offset, layout.max_chests + layout.max_drops) # not currently used @@ -1054,7 +1054,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x142A50, 1) # StandingItemsOn multiClientFlags = ((0x1 if world.dropshuffle[player] else 0) | (0x2 if world.shopsanity[player] else 0) - | (0x4 if world.retro[player] else 0) + | (0x4 if world.retro[player] or world.goal[player] in 'z1' else 0) | (0x8 if world.pottery[player] != 'none' else 0) | (0x10 if is_mystery else 0)) rom.write_byte(0x142A51, multiClientFlags) @@ -1271,7 +1271,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): pack_prizes = [prize_replacements.get(prize, prize) for prize in pack_prizes] dig_prizes = [prize_replacements.get(prize, prize) for prize in dig_prizes] - if world.retro[player]: + if world.rupee_bow[player]: prize_replacements = {0xE1: 0xDA, # 5 Arrows -> Blue Rupee 0xE2: 0xDB} # 10 Arrows -> Red Rupee pack_prizes = [prize_replacements.get(prize, prize) for prize in pack_prizes] @@ -1310,7 +1310,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): 0x12, 0x01, 0x35, 0xFF, # lamp -> 5 rupees 0x51, 0x00 if world.bombbag[player] else 0x06, 0x31 if world.bombbag[player] else 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade. If bombbag -> turns into Bombs (10) 0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade - 0x58, 0x01, 0x36 if world.retro[player] else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode) + 0x58, 0x01, 0x36 if world.rupee_bow[player] else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode) 0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20 0x17, difficulty.heart_piece_limit, 0x47, 0xff, # piece of heart -> green 20 0xFF, 0xFF, 0xFF, 0xFF, # end of table sentinel @@ -1525,7 +1525,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # set up goals for treasure hunt rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28]) - if world.goal[player] in ['triforcehunt', 'trinity']: + if world.goal[player] in ['triforcehunt', 'trinity', 'z1']: rom.write_bytes(0x180167, int16_as_bytes(world.treasure_hunt_count[player])) rom.write_byte(0x180194, 1) # Must turn in triforce pieces (instant win not enabled) @@ -1601,6 +1601,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x18003E, 0x02) # make ganon invincible until all dungeons are beat elif world.goal[player] in ['crystals', 'trinity']: rom.write_byte(0x18003E, 0x04) # make ganon invincible until all crystals + elif world.goal[player] in ['z1']: + rom.write_byte(0x18003E, 0x05) # make ganon invincible until sufficient triforce pieces else: rom.write_byte(0x18003E, 0x03) # make ganon invincible until all crystals and aga 2 are collected @@ -1779,17 +1781,17 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): write_int16(rom, 0x18017A, get_reveal_bytes('Green Pendant') if world.mapshuffle[player] else 0x0000) # Sahasrahla reveal write_int16(rom, 0x18017C, get_reveal_bytes('Crystal 5')|get_reveal_bytes('Crystal 6') if world.mapshuffle[player] else 0x0000) # Bomb Shop Reveal - rom.write_byte(0x180172, 0x01 if world.retro[player] else 0x00) # universal keys - rom.write_byte(0x180175, 0x01 if world.retro[player] else 0x00) # rupee bow - rom.write_byte(0x180176, 0x0A if world.retro[player] else 0x00) # wood arrow cost - rom.write_byte(0x180178, 0x32 if world.retro[player] else 0x00) # silver arrow cost - rom.write_byte(0x301FC, 0xDA if world.retro[player] else 0xE1) # rupees replace arrows under pots + rom.write_byte(0x180172, 0x01 if world.universal_keys[player] else 0x00) # universal keys + rom.write_byte(0x180175, 0x01 if world.rupee_bow[player] else 0x00) # rupee bow + rom.write_byte(0x180176, 0x0A if world.rupee_bow[player] else 0x00) # wood arrow cost + rom.write_byte(0x180178, 0x32 if world.rupee_bow[player] else 0x00) # silver arrow cost + rom.write_byte(0x301FC, 0xDA if world.rupee_bow[player] else 0xE1) # rupees replace arrows under pots if enemized: - rom.write_byte(0x1B152e, 0xDA if world.retro[player] else 0xE1) - rom.write_byte(0x30052, 0xDB if world.retro[player] else 0xE2) # replace arrows in fish prize from bottle merchant - rom.write_bytes(0xECB4E, [0xA9, 0x00, 0xEA, 0xEA] if world.retro[player] else [0xAF, 0x77, 0xF3, 0x7E]) # Thief steals rupees instead of arrows - rom.write_bytes(0xF0D96, [0xA9, 0x00, 0xEA, 0xEA] if world.retro[player] else [0xAF, 0x77, 0xF3, 0x7E]) # Pikit steals rupees instead of arrows - rom.write_bytes(0xEDA5, [0x35, 0x41] if world.retro[player] else [0x43, 0x44]) # Chest game gives rupees instead of arrows + rom.write_byte(0x1B152e, 0xDA if world.rupee_bow[player] else 0xE1) + rom.write_byte(0x30052, 0xDB if world.rupee_bow[player] else 0xE2) # replace arrows in fish prize from bottle merchant + rom.write_bytes(0xECB4E, [0xA9, 0x00, 0xEA, 0xEA] if world.rupee_bow[player] else [0xAF, 0x77, 0xF3, 0x7E]) # Thief steals rupees instead of arrows + rom.write_bytes(0xF0D96, [0xA9, 0x00, 0xEA, 0xEA] if world.rupee_bow[player] else [0xAF, 0x77, 0xF3, 0x7E]) # Pikit steals rupees instead of arrows + rom.write_bytes(0xEDA5, [0x35, 0x41] if world.rupee_bow[player] else [0x43, 0x44]) # Chest game gives rupees instead of arrows digging_game_rng = random.randint(1, 30) # set rng for digging game rom.write_byte(0x180020, digging_game_rng) rom.write_byte(0xEFD95, digging_game_rng) @@ -2669,6 +2671,8 @@ def write_strings(rom, world, player, team): if world.goal[player] in ['dungeons']: tt['sign_ganon'] = 'You need to complete all the dungeons.' + elif world.goal[player] in ['z1']: + tt['sign_ganon'] = 'You need %d triforce pieces from bosses to beat Ganon.' % int(world.treasure_hunt_count[player]) tt['uncle_leaving_text'] = Uncle_texts[random.randint(0, len(Uncle_texts) - 1)] tt['end_triforce'] = "{NOBORDER}\n" + Triforce_texts[random.randint(0, len(Triforce_texts) - 1)] diff --git a/Rules.py b/Rules.py index c6c9b195..8a913a32 100644 --- a/Rules.py +++ b/Rules.py @@ -65,6 +65,8 @@ def set_rules(world, player): for location in world.get_region('Hyrule Castle Courtyard', player).locations: if location.name == 'Murahdahla': add_rule(location, lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player])) + elif world.goal[player] in ['z1']: + add_rule(world.get_location('Ganon', player), lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player])) # if swamp and dam have not been moved we require mirror for swamp palace if not world.swamp_patch_required[player]: @@ -648,7 +650,7 @@ def global_rules(world, player): world.get_location('Ganon', player), lambda state: (state.has_real_sword(player, 2) or state.has_special_weapon_level(player, 3)) and state.has_fire_source(player) - and state.has_crystals(world.crystals_needed_for_ganon[player], player) + and (state.has_crystals(world.crystals_needed_for_ganon[player], player) or world.goal[player] == 'z1') and (state.has_real_sword(player, 3) or state.can_hit_stunned_ganon(player) or state.has_real_sword(player, 2) and @@ -2144,7 +2146,7 @@ def add_key_logic_rules(world, player): add_rule(big_chest, create_rule(d_logic.bk_name, player)) if len(d_logic.bk_doors) == 0 and len(d_logic.bk_chests) <= 1: set_always_allow(big_chest, allow_big_key_in_big_chest(d_logic.bk_name, player)) - if world.retro[player]: + if world.universal_keys[player]: for d_name, layout in world.key_layout[player].items(): for door in layout.flat_prop: if world.mode[player] != 'standard' or not retro_in_hc(door.entrance): diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index bad25891..649718e2 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -57,7 +57,8 @@ "dungeons", "triforcehunt", "trinity", - "crystals" + "crystals", + "z1" ] }, "difficulty": { diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index f2f0ead0..dfd4b294 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -113,7 +113,8 @@ "Triforce Hunt: Places 30 Triforce Pieces in the world, collect", " 20 of them to beat the game.", "Trinity: Can beat the game by defeating Ganon, pulling", - " Pedestal, or delivering Triforce Pieces." + " Pedestal, or delivering Triforce Pieces.", + "Zelda I: Collect triforce pieces off bosses then defeat Ganon." ], "difficulty": [ "Select game difficulty. Affects available itempool. (default: %(default)s)",