From 9a7e4c5f16b0034248894ecc844bc72a50d7fdf8 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 30 Dec 2021 22:41:50 -0600 Subject: [PATCH] Added new Trinity goal --- BaseClasses.py | 4 ++-- Fill.py | 2 +- ItemList.py | 22 +++++++++---------- Mystery.py | 5 +++-- Rom.py | 11 ++++++---- Rules.py | 6 ++--- mystery_example.yml | 1 + resources/app/cli/lang/en.json | 4 +++- resources/app/gui/lang/en.json | 1 + resources/app/gui/randomize/item/widgets.json | 1 + 10 files changed, 33 insertions(+), 24 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index bca95ef8..54ef0860 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2968,7 +2968,7 @@ class Spoiler(object): outfile.write('Retro:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['retro'][player] else 'No')) 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] == 'triforcehunt': + if self.metadata['goal'][player] in ['triforcehunt', 'trinity']: 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])) @@ -3215,7 +3215,7 @@ world_mode = {"open": 0, "standard": 1, "inverted": 2} sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3} # byte 2: GGGD DFFH (goal, diff, item_func, hints) -goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "crystals": 4} +goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "crystals": 4, "trinity": 5} diff_mode = {"normal": 0, "hard": 1, "expert": 2} func_mode = {"normal": 0, "hard": 1, "expert": 2} diff --git a/Fill.py b/Fill.py index 2a74d222..59358af0 100644 --- a/Fill.py +++ b/Fill.py @@ -358,7 +358,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None if not gftower_trash or not world.ganonstower_vanilla[player] or world.doorShuffle[player] == 'crossed' or world.logic[player] in ['owglitches', 'nologic']: continue - gftower_trash_count = (random.randint(15, 50) if world.goal[player] == 'triforcehunt' else random.randint(0, 15)) + gftower_trash_count = (random.randint(15, 50) if world.goal[player] in ['triforcehunt', 'trinity'] else random.randint(0, 15)) gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player] random.shuffle(gtower_locations) diff --git a/ItemList.py b/ItemList.py index 6307faf7..30dcc6c3 100644 --- a/ItemList.py +++ b/ItemList.py @@ -178,7 +178,7 @@ def get_custom_array_key(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', 'crystals'] + if (world.difficulty[player] not in ['normal', 'hard', 'expert'] or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'trinity', 'crystals'] 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') @@ -190,7 +190,7 @@ def generate_itempool(world, player): else: world.push_item(world.get_location('Ganon', player), ItemFactory('Triforce', player), False) - if world.goal[player] in ['triforcehunt']: + if world.goal[player] in ['triforcehunt', 'trinity']: region = world.get_region('Hyrule Castle Courtyard',player) loc = Location(player, "Murahdahla", parent=region) @@ -337,7 +337,7 @@ def generate_itempool(world, player): if clock_mode is not None: world.clock_mode = clock_mode - if world.goal[player] == 'triforcehunt': + if world.goal[player] in ['triforcehunt', 'trinity']: if world.treasure_hunt_count[player] == 0: world.treasure_hunt_count[player] = 20 if world.treasure_hunt_total[player] == 0: @@ -759,7 +759,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, placed_items = {} precollected_items = [] clock_mode = None - if goal == 'triforcehunt': + if goal in ['triforcehunt', 'trinity']: if treasure_hunt_total == 0: treasure_hunt_total = 30 triforcepool = ['Triforce Piece'] * int(treasure_hunt_total) @@ -841,7 +841,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, place_item('Link\'s Uncle', swords_to_use.pop()) place_item('Blacksmith', swords_to_use.pop()) place_item('Pyramid Fairy - Left', swords_to_use.pop()) - if goal != 'pedestal': + if goal not in ['pedestal', 'trinity']: place_item('Master Sword Pedestal', swords_to_use.pop()) else: place_item('Master Sword Pedestal', 'Triforce') @@ -866,7 +866,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, pool.extend(diff.timedohko) extraitems -= len(diff.timedohko) clock_mode = 'countdown-ohko' - if goal == 'triforcehunt': + if goal in ['triforcehunt', 'trinity']: pool.extend(triforcepool) extraitems -= len(triforcepool) @@ -877,7 +877,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, pool.extend(extra) extraitems -= len(extra) - if goal == 'pedestal' and swords != 'vanilla': + if goal in ['pedestal', 'trinity'] and swords != 'vanilla': place_item('Master Sword Pedestal', 'Triforce') if retro: pool = [item.replace('Single Arrow','Rupees (5)') for item in pool] @@ -958,7 +958,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s treasure_hunt_count = max(min(customitemarray["triforcepiecesgoal"], 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["triforcepieces"] < treasure_hunt_count) and (goal == 'triforcehunt') and (customitemarray["triforce"] == 0): + if (customitemarray["triforcepieces"] < treasure_hunt_count) and (goal in ['triforcehunt', 'trinity']) and (customitemarray["triforce"] == 0): extrapieces = treasure_hunt_count - customitemarray["triforcepieces"] pool.extend(['Triforce Piece'] * extrapieces) itemtotal = itemtotal + extrapieces @@ -970,7 +970,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s elif timer == 'ohko': clock_mode = 'ohko' - if goal == 'pedestal': + if goal in ['pedestal', 'trinity']: place_item('Master Sword Pedestal', 'Triforce') itemtotal = itemtotal + 1 @@ -1008,7 +1008,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s # 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']: + for goal in ['ganon', 'triforcehunt', 'pedestal', 'trinity']: 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']: @@ -1022,7 +1022,7 @@ def test(): count = len(out[0]) + len(out[1]) correct_count = total_items_to_place - if goal == 'pedestal' and swords != 'vanilla': + if goal in ['pedestal', 'trinity'] and swords != 'vanilla': # pedestal goals generate one extra item correct_count += 1 if retro: diff --git a/Mystery.py b/Mystery.py index 7008c0d5..499a1ee4 100644 --- a/Mystery.py +++ b/Mystery.py @@ -179,9 +179,10 @@ def roll_settings(weights): 'fast_ganon': 'crystals', 'dungeons': 'dungeons', 'pedestal': 'pedestal', - 'triforce-hunt': 'triforcehunt' + 'triforce-hunt': 'triforcehunt', + 'trinity': 'trinity' }[goal] - ret.openpyramid = goal == 'fast_ganon' if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False + ret.openpyramid = goal in ['fast_ganon', 'trinity'] if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False ret.shuffleganon = get_choice('shuffleganon') == 'on' diff --git a/Rom.py b/Rom.py index 81fa4cb4..847aa0a8 100644 --- a/Rom.py +++ b/Rom.py @@ -755,7 +755,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.experimental[player] and world.goal[player] != 'triforcehunt': + if world.experimental[player] and world.goal[player] not in ['triforcehunt', 'trinity']: dr_flags |= DROptions.Debug if world.doorShuffle[player] == 'crossed' and world.logic[player] != 'nologic'\ and world.mixed_travel[player] == 'prevent': @@ -1235,7 +1235,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] == 'triforcehunt': + if world.goal[player] in ['triforcehunt', 'trinity']: rom.write_byte(0x180167, int(world.treasure_hunt_count[player]) % 256) rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled) @@ -1261,7 +1261,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest rom.write_byte(0x50599, 0x00) # disable below ganon chest rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest - rom.write_byte(0x18008B, 0x01 if world.open_pyramid[player] else 0x00) # pre-open Pyramid Hole + rom.write_byte(0x18008B, 0x01 if world.open_pyramid[player] or world.goal[player] == 'trinity' else 0x00) # pre-open Pyramid Hole rom.write_byte(0x18008C, 0x01 if world.crystals_needed_for_gt[player] == 0 else 0x00) # GT pre-opened if crystal requirement is 0 rom.write_byte(0xF5D73, 0xF0) # bees are catchable rom.write_byte(0xF5F10, 0xF0) # bees are catchable @@ -1446,7 +1446,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x18003E, 0x01) # make ganon invincible 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']: + elif world.goal[player] in ['crystals', 'trinity']: rom.write_byte(0x18003E, 0x04) # make ganon invincible until all crystals else: rom.write_byte(0x18003E, 0x03) # make ganon invincible until all crystals and aga 2 are collected @@ -2374,6 +2374,9 @@ def write_strings(rom, world, player, team): tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' tt['sign_ganon'] = 'You need to get to the pedestal... Ganon is invincible!' else: + if world.goal[player] in ['trinity']: + tt['sign_ganon'] = 'Three ways Ganon can die! Get to it!' + tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player]) 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!' diff --git a/Rules.py b/Rules.py index b3e1a7e6..c509b55b 100644 --- a/Rules.py +++ b/Rules.py @@ -55,7 +55,7 @@ def set_rules(world, player): elif world.goal[player] == 'ganon': # require aga2 to beat ganon add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player)) - elif world.goal[player] == 'triforcehunt': + if world.goal[player] in ['triforcehunt', 'trinity']: if ('Murahdahla', player) in world._location_cache: add_rule(world.get_location('Murahdahla', player), lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player])) @@ -1009,7 +1009,7 @@ def ow_rules(world, player): if not world.is_tile_swapped(0x1b, player): set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: False) set_rule(world.get_entrance('Inverted Pyramid Entrance', player), lambda state: False) - set_rule(world.get_entrance('Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.open_pyramid[player]) + set_rule(world.get_entrance('Pyramid Hole', player), lambda state: world.open_pyramid[player] or world.goal[player] == 'trinity' or state.has('Beat Agahnim 2', player)) set_rule(world.get_entrance('HC Area Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('HC Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -1020,7 +1020,7 @@ def ow_rules(world, player): set_rule(world.get_entrance('Top of Pyramid', player), lambda state: state.has('Beat Agahnim 1', player)) set_rule(world.get_entrance('Top of Pyramid (Inner)', player), lambda state: state.has('Beat Agahnim 1', player)) else: - set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.open_pyramid[player]) + set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: world.open_pyramid[player] or world.goal[player] == 'trinity' or state.has('Beat Agahnim 2', player)) set_rule(world.get_entrance('Pyramid Hole', player), lambda state: False) set_rule(world.get_entrance('Pyramid Entrance', player), lambda state: False) diff --git a/mystery_example.yml b/mystery_example.yml index d6fa1eb5..3869fef9 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -73,6 +73,7 @@ dungeons: 1 pedestal: 2 triforce-hunt: 2 + trinity: 1 triforce_goal_min: 10 triforce_goal_max: 30 triforce_pool_min: 20 diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 2f5d962d..e0861c23 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -103,7 +103,9 @@ "All Dungeons: Collect all crystals, pendants, beat both", " Agahnim fights and then defeat Ganon.", "Triforce Hunt: Places 30 Triforce Pieces in the world, collect", - " 20 of them to beat the game." + " 20 of them to beat the game.", + "Trinity: Can beat the game by defeating Ganon, pulling", + " Pedestal, or delivering Triforce Pieces." ], "difficulty": [ "Select game difficulty. Affects available itempool. (default: %(default)s)", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index a057e5ab..72694f6b 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -238,6 +238,7 @@ "randomizer.item.goal.pedestal": "Master Sword Pedestal", "randomizer.item.goal.dungeons": "All Dungeons", "randomizer.item.goal.triforcehunt": "Triforce Hunt", + "randomizer.item.goal.trinity": "Trinity", "randomizer.item.goal.crystals": "Crystals", "randomizer.item.crystals_gt": "Crystals to open GT", diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index 038e668c..e33dee02 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -35,6 +35,7 @@ "pedestal", "dungeons", "triforcehunt", + "trinity", "crystals" ] },