diff --git a/BaseClasses.py b/BaseClasses.py index d66f6be7..2176eb2a 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -518,6 +518,9 @@ class CollectionState(object): def item_count(self, item, player: int) -> int: return self.prog_items[item, player] + def has_triforce_pieces(self, count: int, player: int) -> bool: + return self.item_count('Triforce Piece', player) + self.item_count('Power Star', player) >= count + def has_crystals(self, count: int, player: int) -> bool: crystals = ['Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'] return len([crystal for crystal in crystals if self.has(crystal, player)]) >= count diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 19f62453..132d02a2 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -67,7 +67,7 @@ def parse_arguments(argv, no_defaults=False): Vanilla: Swords are in vanilla locations. ''') parser.add_argument('--goal', default=defval('ganon'), const='ganon', nargs='?', - choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'localtriforcehunt', 'crystals'], + choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'localtriforcehunt', 'ganontriforcehunt', 'localganontriforcehunt', 'crystals'], help='''\ Select completion goal. (default: %(default)s) Ganon: Collect all crystals, beat Agahnim 2 then @@ -79,7 +79,11 @@ def parse_arguments(argv, no_defaults=False): Triforce Hunt: Places 30 Triforce Pieces in the world, collect 20 of them to beat the game. Local Triforce Hunt: Places 30 Triforce Pieces in your world, collect - 20 of them to beat the game. + 20 of them to beat the game. + Ganon Triforce Hunt: Places 30 Triforce Pieces in the world, collect + 20 of them, then defeat Ganon. + Local Ganon Triforce Hunt: Places 30 Triforce Pieces in your world, + collect 20 of them, then defeat Ganon. ''') parser.add_argument('--triforce_pieces_available', default=defval(30), type=lambda value: min(max(int(value), 1), 90), diff --git a/Fill.py b/Fill.py index 0d06ce3e..93be4fc8 100644 --- a/Fill.py +++ b/Fill.py @@ -244,8 +244,8 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None continue gftower_trash_count = ( - random.randint(15, 50) if world.goal[player] in {'triforcehunt', 'localtriforcehunt'} else random.randint(0, - 15)) + random.randint(15, 50) if 'triforcehunt' in world.goal[player] + else random.randint(0, 15)) gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player] diff --git a/Gui.py b/Gui.py index 61e3aebb..e0574986 100755 --- a/Gui.py +++ b/Gui.py @@ -240,7 +240,7 @@ def guiMain(args=None): goalVar = StringVar() goalVar.set('ganon') goalOptionMenu = OptionMenu(goalFrame, goalVar, 'ganon', 'pedestal', 'dungeons', 'triforcehunt', - 'localtriforcehunt', 'crystals') + 'localtriforcehunt', 'ganontriforcehunt', 'localganontriforcehunt', 'crystals') goalOptionMenu.pack(side=RIGHT) goalLabel = Label(goalFrame, text='Game goal') goalLabel.pack(side=LEFT) diff --git a/ItemList.py b/ItemList.py index 62f6b662..c70eb5fb 100644 --- a/ItemList.py +++ b/ItemList.py @@ -124,7 +124,7 @@ difficulties = { def generate_itempool(world, player): if world.difficulty[player] not in ['normal', 'hard', 'expert']: raise NotImplementedError(f"Diffulty {world.difficulty[player]}") - if world.goal[player] not in {'ganon', 'pedestal', 'dungeons', 'triforcehunt', 'localtriforcehunt', 'crystals'}: + if world.goal[player] not in {'ganon', 'pedestal', 'dungeons', 'triforcehunt', 'localtriforcehunt', 'ganontriforcehunt', 'localganontriforcehunt', 'crystals'}: raise NotImplementedError(f"Goal {world.goal[player]}") if world.mode[player] not in {'open', 'standard', 'inverted'}: raise NotImplementedError(f"Mode {world.mode[player]}") @@ -142,9 +142,8 @@ def generate_itempool(world, player): region = world.get_region('Light World', player) loc = Location(player, "Murahdahla", parent=region) - loc.access_rule = lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', - player) >= \ - state.world.treasure_hunt_count[player] + loc.access_rule = lambda state: state.has_triforce_pieces(state.world.treasure_hunt_count[player], player) + region.locations.append(loc) world.dynamic_locations.append(loc) @@ -261,7 +260,7 @@ def generate_itempool(world, player): random.shuffle(nonprogressionitems) triforce_pieces = world.triforce_pieces_available[player] - if world.goal[player] in {'triforcehunt', 'localtriforcehunt'} and triforce_pieces > 30: + if 'triforcehunt' in world.goal[player] and triforce_pieces > 30: progressionitems += [ItemFactory("Triforce Piece", player)] * (triforce_pieces - 30) nonprogressionitems = nonprogressionitems[(triforce_pieces-30):] @@ -510,7 +509,7 @@ def get_pool_core(world, player: int): pool.extend(diff.timedohko) extraitems -= len(diff.timedohko) clock_mode = 'countdown-ohko' - if goal in {'triforcehunt', 'localtriforcehunt'}: + if 'triforcehunt' in goal: while len(diff.triforcehunt) > world.triforce_pieces_available[player]: diff.triforcehunt.pop() pool.extend(diff.triforcehunt) @@ -643,8 +642,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s treasure_hunt_count = max(min(customitemarray[67], 99), 1) #To display, count must be between 1 and 99. treasure_hunt_icon = 'Triforce Piece' # Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling. - if (customitemarray[66] < treasure_hunt_count) and (goal in {'triforcehunt', 'localtriforcehunt'}) and ( - customitemarray[68] == 0): + if (customitemarray[66] < treasure_hunt_count) and ('triforcehunt' in goal) and (customitemarray[68] == 0): extrapieces = treasure_hunt_count - customitemarray[66] pool.extend(['Triforce Piece'] * extrapieces) itemtotal = itemtotal + extrapieces diff --git a/LICENSE b/LICENSE index 2d235b1c..ea359bf8 100644 --- a/LICENSE +++ b/LICENSE @@ -2,6 +2,7 @@ MIT License Copyright (c) 2017 LLCoolDave Copyright (c) 2020 Berserker66 +Copyright (c) 2020 CaitSith2 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Mystery.py b/Mystery.py index a20fdb0c..6038694d 100644 --- a/Mystery.py +++ b/Mystery.py @@ -288,9 +288,11 @@ def roll_settings(weights): 'pedestal': 'pedestal', 'triforce_hunt': 'triforcehunt', 'triforce-hunt': 'triforcehunt', # deprecated, moving all goals to `_` - 'local_triforce_hunt': 'localtriforcehunt' + 'local_triforce_hunt': 'localtriforcehunt', + 'ganon_triforce_hunt': 'ganontriforcehunt', + 'local_ganon_triforce_hunt': 'localganontriforcehunt' }[goal] - ret.openpyramid = goal == 'fast_ganon' + ret.openpyramid = goal in ['fast_ganon', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt'] ret.crystals_gt = get_choice('tower_open', weights) diff --git a/Rom.py b/Rom.py index dc87d469..5b99d6f4 100644 --- a/Rom.py +++ b/Rom.py @@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'e7eee92d3a89283f591fdf7ac66a4ab7' +RANDOMIZERBASEHASH = 'a567da86e8bd499256da4bba2209a3fd' class LocalRom(object): @@ -1084,6 +1084,8 @@ def patch_rom(world, rom, player, team, enemized): if world.goal[player] in ['pedestal', 'triforcehunt', 'localtriforcehunt']: rom.write_byte(0x18003E, 0x01) # make ganon invincible + elif world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']: + rom.write_byte(0x18003E, 0x05) # make ganon invincible until 20 triforce pieces are collected elif world.goal[player] in ['dungeons']: rom.write_byte(0x18003E, 0x02) # make ganon invincible until all dungeons are beat elif world.goal[player] in ['crystals']: @@ -1778,6 +1780,10 @@ def write_strings(rom, world, player, team): tt['ganon_fall_in'] = Ganon1_texts[random.randint(0, len(Ganon1_texts) - 1)] 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!' + if world.goal[player] == 'ganontriforcehunt' and world.players > 1: + tt['sign_ganon'] = 'You need to find %d Triforce pieces with your friends to defeat Ganon.' % world.treasure_hunt_count[player] + elif world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']: + tt['sign_ganon'] = 'You need to find %d Triforce pieces to defeat Ganon.' % world.treasure_hunt_count[player] tt['kakariko_tavern_fisherman'] = TavernMan_texts[random.randint(0, len(TavernMan_texts) - 1)] diff --git a/Rules.py b/Rules.py index c9951af7..da9eb18b 100644 --- a/Rules.py +++ b/Rules.py @@ -143,7 +143,7 @@ def item_name(state, location, player): def locality_rules(world, player): - if world.goal[player] == "localtriforcehunt": + if world.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]: world.local_items[player].add('Triforce Piece') if world.local_items[player]: for location in world.get_locations(): @@ -418,8 +418,13 @@ def global_rules(world, player): 'Ganons Tower - Pre-Moldorm Chest', 'Ganons Tower - Validation Chest']: forbid_item(world.get_location(location, player), 'Big Key (Ganons Tower)', player) - set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player) - and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (state.has('Silver Arrows', player) and state.can_shoot_arrows(player)) or state.has('Lamp', player) or state.can_extend_magic(player, 12))) # need to light torch a sufficient amount of times + + if world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']: + set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and state.has_triforce_pieces(world.treasure_hunt_count[player], player) + and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (state.has('Silver Arrows', player) and state.can_shoot_arrows(player)) or state.has('Lamp', player) or state.can_extend_magic(player, 12))) # need to light torch a sufficient amount of times + else: + set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player) + and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (state.has('Silver Arrows', player) and state.can_shoot_arrows(player)) or state.has('Lamp', player) or state.can_extend_magic(player, 12))) # need to light torch a sufficient amount of times set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has_beam_sword(player)) # need to damage ganon to get tiles to drop @@ -817,7 +822,10 @@ def swordless_rules(world, player): set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player)) set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player)) # no curtain set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) #in swordless mode bombos pads are present in the relevant parts of ice palace - 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], player)) + if world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']: + 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_triforce_pieces(world.treasure_hunt_count[player], player)) + else: + 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], 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[player] != 'inverted': diff --git a/data/basepatch.bmbp b/data/basepatch.bmbp index e6b775fb..944a8588 100644 Binary files a/data/basepatch.bmbp and b/data/basepatch.bmbp differ diff --git a/easy.yaml b/easy.yaml index 8b1f9363..d3c9d222 100644 --- a/easy.yaml +++ b/easy.yaml @@ -80,6 +80,8 @@ goals: pedestal: 0 # Pull the Triforce from the Master Sword pedestal triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then turn them in to Murahadala in front of Hyrule Castle local_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle + ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then kill Ganon + local_ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then kill Ganon triforce_pieces_available: # set to how many triforces pieces are available to collect in the world. 30 is the default. Max is 112, Min is 1. # format "pieces: chance" 25: 0