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 01c26f25..9f2e0d7b 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 82fe3084..591bc892 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -72,6 +72,7 @@ "trinity", "crystals", "ganonhunt", + "bosshunt", "completionist", "sanctuary" ] @@ -299,6 +300,27 @@ "random" ] }, + "bosses_ganon": { + "choices": [ + "12", + "11", + "10", + "9", + "8", + "7", + "6", + "5", + "4", + "3", + "2", + "1", + "0" + ] + }, + "bosshunt_include_agas": { + "action": "store_true", + "type": "bool" + }, "crystals_gt": { "choices": [ "7",