From 67307a872c2a8170dde2fd675c11505d57a83321 Mon Sep 17 00:00:00 2001 From: Kara Alexandra Date: Wed, 21 Jan 2026 08:20:59 -0600 Subject: [PATCH] Bosshunt mode --- BaseClasses.py | 17 ++++++++++--- CLI.py | 4 ++- ItemList.py | 8 +++--- Main.py | 20 +++++++++------ Rom.py | 47 +++++++++++++++++++++++++++++------- Rules.py | 9 +++++++ data/base2current.bps | Bin 155482 -> 155589 bytes resources/app/cli/args.json | 22 +++++++++++++++++ 8 files changed, 104 insertions(+), 23 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index ddf1284a..0b044a49 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -74,6 +74,8 @@ class World(object): self.dark_rooms = {} self.damage_challenge = {} self.shuffle_damage_table = {} + self.bosses_ganon = {} + self.bosshunt_include_agas = {} self.ganon_item = {} self.ganon_item_orig = {} self.custom = custom @@ -168,6 +170,8 @@ class World(object): set_player_attr('escape_assist', []) set_player_attr('crystals_needed_for_ganon', 7) set_player_attr('crystals_needed_for_gt', 7) + set_player_attr('bosses_ganon', 8) + set_player_attr('bosshunt_include_agas', False) set_player_attr('ganon_item', 'silver') set_player_attr('crystals_ganon_orig', {}) set_player_attr('crystals_gt_orig', {}) @@ -356,7 +360,7 @@ class World(object): else: if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district']: return False - elif self.goal[player] in ['crystals', 'trinity', 'ganonhunt']: + elif self.goal[player] in ['crystals', 'trinity', 'ganonhunt', 'bosshunt']: return True else: return False @@ -3118,6 +3122,8 @@ class Spoiler(object): 'beemizer': self.world.beemizer, 'gt_crystals': self.world.crystals_needed_for_gt, 'ganon_crystals': self.world.crystals_needed_for_ganon, + 'ganon_bosses': self.world.bosses_ganon, + 'bosshunt_include_agas': self.world.bosshunt_include_agas, 'ganon_item': self.world.ganon_item, 'open_pyramid': self.world.open_pyramid, 'accessibility': self.world.accessibility, @@ -3340,6 +3346,10 @@ class Spoiler(object): if custom['ganongoal'] and 'requirements' in custom['ganongoal']: outfile.write('Ganon Requirement:'.ljust(line_width) + 'custom\n') outfile.write(' %s\n' % custom['ganongoal']['goaltext']) + elif self.metadata['goal'][player] == 'bosshunt': + outfile.write('Ganon Requirement:'.ljust(line_width) + '%s bosses%s\n' % + (str(self.world.bosses_ganon[player]), + ' (including both Agahnims)' if self.world.bosshunt_include_agas[player] else '')) else: outfile.write('Ganon Requirement:'.ljust(line_width) + '%s crystals\n' % str(self.world.crystals_ganon_orig[player])) if custom['pedgoal'] and 'requirements' in custom['pedgoal']: @@ -3749,8 +3759,9 @@ 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, 'trinity': 5, - 'ganonhunt': 6, 'completionist': 7, 'sanctuary': 1} +goal_mode = {'ganon': 0, 'pedestal': 1, 'dungeons': 2, 'triforcehunt': 3, + 'crystals': 4, 'trinity': 5, 'ganonhunt': 6, 'completionist': 7, + 'sanctuary': 1, 'bosshunt': 6} diff_mode = {"normal": 0, "hard": 1, "expert": 2} func_mode = {"normal": 0, "hard": 1, "expert": 2} diff --git a/CLI.py b/CLI.py index b2ef18a1..c3efe785 100644 --- a/CLI.py +++ b/CLI.py @@ -134,7 +134,7 @@ def parse_cli(argv, no_defaults=False): for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'ow_shuffle', 'ow_terrain', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle', 'flute_mode', 'bow_mode', 'take_any', 'boots_hint', 'shuffle_followers', - 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'ganon_item', 'openpyramid', + 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'bosses_ganon', 'bosshunt_include_agas', 'ganon_item', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'prizeshuffle', 'showloot', 'showmap', 'startinventory', 'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items', 'triforce_max_difference', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', @@ -178,6 +178,8 @@ def parse_settings(): "goal": "ganon", "crystals_gt": "7", "crystals_ganon": "7", + "bosses_ganon": "8", + "bosshunt_include_agas": False, "ganon_item": "silver", "swords": "random", "flute_mode": "normal", diff --git a/ItemList.py b/ItemList.py index 4202719a..4b2befd3 100644 --- a/ItemList.py +++ b/ItemList.py @@ -218,12 +218,14 @@ 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', 'trinity', 'crystals', - 'ganonhunt', 'completionist', 'sanctuary'] + or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', + 'triforcehunt', 'trinity', 'crystals', + 'ganonhunt', 'completionist', 'sanctuary', + 'bosshunt'] 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') + raise NotImplementedError('Not supported yet') if world.timer in ['ohko', 'timed-ohko']: world.can_take_damage[player] = False diff --git a/Main.py b/Main.py index 29b7e53b..2e99b625 100644 --- a/Main.py +++ b/Main.py @@ -80,7 +80,7 @@ def random_ganon_item(sword_mode): def main(args, seed=None, fish=None): check_python_version() - + if args.print_template_yaml: return export_yaml(args, fish) @@ -124,14 +124,14 @@ def main(args, seed=None, fish=None): from OverworldShuffle import __version__ as ORVersion logger.info( - world.fish.translate("cli","cli","app.title") + "\n", - ORVersion, - "%s (%s)" % (world.seed, str(args.outputname)) if str(args.outputname).startswith('M') else world.seed, - Settings.make_code(world, 1) if world.players == 1 else '' + world.fish.translate("cli","cli","app.title") + "\n", + ORVersion, + "%s (%s)" % (world.seed, str(args.outputname)) if str(args.outputname).startswith('M') else world.seed, + Settings.make_code(world, 1) if world.players == 1 else '' ) for k,v in {"DR":__version__,"OR":ORVersion}.items(): - logger.info((k + ' Version:').ljust(16) + '%s' % v) + logger.info((k + ' Version:').ljust(16) + '%s' % v) parsed_names = parse_player_names(args.names, world.players, args.teams) world.teams = len(parsed_names) @@ -478,6 +478,8 @@ def init_world(args, fish): world.crystals_ganon_orig = args.crystals_ganon.copy() world.crystals_gt_orig = args.crystals_gt.copy() world.ganon_item_orig = args.ganon_item.copy() + world.bosses_ganon = {player: int(args.bosses_ganon[player]) for player in range(1, world.players + 1)} + world.bosshunt_include_agas = args.bosshunt_include_agas.copy() world.owTerrain = args.ow_terrain.copy() world.owKeepSimilar = args.ow_keepsimilar.copy() world.owWhirlpoolShuffle = args.ow_whirlpool.copy() @@ -525,7 +527,7 @@ def init_world(args, fish): world.money_balance = args.money_balance.copy() # custom settings - these haven't been promoted to full settings yet - in_progress_settings = ['force_enemy', 'free_lamp_cone'] + in_progress_settings = ['force_enemy'] for player in range(1, world.players + 1): for setting in in_progress_settings: if world.customizer and world.customizer.has_setting(player, setting): @@ -795,6 +797,8 @@ def copy_world(world): ret.free_lamp_cone = world.free_lamp_cone.copy() ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy() ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() + ret.bosses_ganon = world.bosses_ganon.copy() + ret.bosshunt_include_agas = world.bosshunt_include_agas.copy() ret.ganon_item = world.ganon_item.copy() ret.crystals_ganon_orig = world.crystals_ganon_orig.copy() ret.crystals_gt_orig = world.crystals_gt_orig.copy() @@ -1024,6 +1028,8 @@ def copy_world_premature(world, player, create_flute_exits=True): ret.free_lamp_cone = world.free_lamp_cone.copy() ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy() ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() + ret.bosses_ganon = world.bosses_ganon.copy() + ret.bosshunt_include_agas = world.bosshunt_include_agas.copy() ret.ganon_item = world.ganon_item.copy() ret.crystals_ganon_orig = world.crystals_ganon_orig.copy() ret.crystals_gt_orig = world.crystals_gt_orig.copy() diff --git a/Rom.py b/Rom.py index 95419809..59ee0331 100644 --- a/Rom.py +++ b/Rom.py @@ -44,7 +44,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '015d196d606fe1870a37df38ec335835' +RANDOMIZERBASEHASH = '0a1b516a3ecde44603b6b2dca2b93616' class JsonRom(object): @@ -1211,7 +1211,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None): rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28]) if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt']: rom.write_bytes(0x180167, int16_as_bytes(world.treasure_hunt_count[player])) - rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled) + rom.write_byte(0x180194, 1) # Must turn in triforce pieces (instant win not enabled) rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed @@ -1285,6 +1285,8 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None): # 08: Goal items collected (ie. Triforce Pieces) # 09: Max collection rate # 0A: Custom goal + # 0B: Reserved for Bingo + # 0C: All bosses (prize bosses + aga1 + aga2) def get_goal_bytes(type): goal_bytes = [] @@ -1339,6 +1341,11 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None): ganon_goal += [0x02, world.crystals_needed_for_ganon[player]] elif world.goal[player] in ['ganonhunt']: ganon_goal += [0x88] # triforce pieces + elif world.goal[player] in ['bosshunt']: + if world.bosshunt_include_agas[player]: + ganon_goal += [0x0C, world.bosses_ganon[player]] # total bosses + else: + ganon_goal += [0x05, world.bosses_ganon[player]] # prize bosses elif world.goal[player] in ['completionist']: ganon_goal += [0x81, 0x82, 0x06, 0x07, 0x89] # AD and max collection rate else: @@ -1482,7 +1489,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None): rom.write_byte(loot_icons + crystal_id, crystal_category) pendant_ids = [0x37, 0x38, 0x39] - if world.goal[player] in ['pedestal', 'dungeons']: + if world.goal[player] in ['pedestal', 'dungeons', 'trinity']: pendant_category = 0x0C else: pendant_category = 0x06 @@ -1635,12 +1642,28 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None): # b - Big Key # a - Small Key # + dungeon_items_menu = 0x00 + + if world.doorShuffle[player] not in ['vanilla', 'basic']: + dungeon_items_menu |= 0x0F + + if world.keyshuffle[player] not in ['none', 'universal']: + dungeon_items_menu |= 0x01 + + if world.bigkeyshuffle[player] != 'none': + dungeon_items_menu |= 0x02 + enable_menu_map_check = (world.overworld_map[player] != 'default' and world.shuffle[player] != 'vanilla') or world.prizeshuffle[player] not in ['none', 'dungeon', 'nearby'] - rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] not in ['none', 'universal'] else 0x00) - | (0x02 if world.bigkeyshuffle[player] != 'none' else 0x00) - | (0x04 if world.mapshuffle[player] != 'none' or enable_menu_map_check else 0x00) - | (0x08 if world.compassshuffle[player] != 'none' else 0x00) # free roaming items in menu - | (0x10 if world.logic[player] == 'nologic' else 0))) # boss icon + if world.mapshuffle[player] != 'none' or enable_menu_map_check: + dungeon_items_menu |= 0x04 + + if world.compassshuffle[player] != 'none': + dungeon_items_menu |= 0x08 + + if world.logic[player] == 'nologic' or world.goal[player] == 'bosshunt': + dungeon_items_menu |= 0x10 + + rom.write_byte(0x180045, dungeon_items_menu) def get_reveal_bytes(itemName): if world.prizeshuffle[player] != 'wild': @@ -2746,12 +2769,18 @@ def write_strings(rom, world, player, team): 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]) elif world.goal[player] == 'ganonhunt': tt['sign_ganon'] = 'Go find the Triforce pieces to beat Ganon' + elif world.goal[player] == 'bosshunt': + bosshunt_count = '%d guardian%s of %sdungeons' % \ + (world.bosses_ganon[player], + '' if world.bosses_ganon[player] == 1 else 's', + '' if world.bosshunt_include_agas[player] else 'prize ') + tt['sign_ganon'] = 'To beat Ganon you must defeat %s.' % bosshunt_count elif world.goal[player] == 'completionist': tt['sign_ganon'] = 'Ganon only respects those who have done everything' 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!' - + def get_custom_goal_text(type): goal_text = world.custom_goals[player][type]['goaltext'] placeholder_count = goal_text.count('%d') diff --git a/Rules.py b/Rules.py index 91637cce..b836037b 100644 --- a/Rules.py +++ b/Rules.py @@ -80,6 +80,15 @@ def set_rules(world, player): add_rule(world.get_location('Ganon', player), lambda state: state.has_crystals(world.crystals_needed_for_ganon[player], player)) elif world.goal[player] == 'ganonhunt': 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])) + elif world.goal[player] == 'bosshunt': + if world.bosshunt_include_agas[player]: + add_rule(world.get_location('Ganon', player), lambda state: + state.item_count('Beat Agahnim 1', player) + + state.item_count('Beat Agahnim 2', player) + + state.item_count('Beat Boss', player) >= world.bosses_ganon[player]) + else: + add_rule(world.get_location('Ganon', player), lambda state: + state.item_count('Beat Boss', player) >= world.bosses_ganon[player]) elif world.goal[player] == 'completionist': add_rule(world.get_location('Ganon', player), lambda state: state.everything(player)) diff --git a/data/base2current.bps b/data/base2current.bps index 01c26f256b197ed72f42837aabe8913692c351a9..9f2e0d7b7415df57c30f05e25c05526d1af89cd8 100644 GIT binary patch delta 8645 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 delta 8402 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