From becba348b9f410a0c0b6c09c84d75dae4e24af52 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 1 Dec 2022 14:10:10 -0700 Subject: [PATCH] New goals and rom update --- BaseClasses.py | 13 ++++++-- Fill.py | 2 +- ItemList.py | 28 ++++++++++-------- Main.py | 2 +- RELEASENOTES.md | 17 ++++++++--- Rom.py | 15 ++++++++-- Rules.py | 4 +++ data/base2current.bps | Bin 93456 -> 93489 bytes mystery_example.yml | 2 ++ resources/app/cli/args.json | 4 ++- resources/app/cli/lang/en.json | 5 +++- resources/app/gui/lang/en.json | 2 ++ resources/app/gui/randomize/item/widgets.json | 4 ++- source/tools/MysteryUtils.py | 4 ++- 14 files changed, 74 insertions(+), 28 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index d7724a98..6cbc146e 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -155,7 +155,9 @@ class World(object): def finish_init(self): for player in range(1, self.players + 1): if self.mode[player] == 'retro': - self.mode[player] == 'open' + self.mode[player] = 'open' + if self.goal[player] == 'completionist': + self.accessibility[player] = 'locations' def get_name_string_for_object(self, obj): return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})' @@ -1035,6 +1037,10 @@ class CollectionState(object): def item_count(self, item, player): return self.prog_items[item, player] + def everything(self, player): + return (len([x for x in self.locations_checked if x.player == player]) + >= len(self.world.get_filled_locations(player))) + def has_crystals(self, count, player): 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 @@ -2587,7 +2593,7 @@ class Spoiler(object): outfile.write('Mode: %s\n' % self.metadata['mode'][player]) outfile.write('Swords: %s\n' % self.metadata['weapons'][player]) outfile.write('Goal: %s\n' % self.metadata['goal'][player]) - if self.metadata['goal'][player] in ['triforcehunt', 'trinity']: + if self.metadata['goal'][player] in ['triforcehunt', 'trinity', 'ganonhunt']: outfile.write('Triforce Pieces Required: %s\n' % self.metadata['triforcegoal'][player]) outfile.write('Triforce Pieces Total: %s\n' % self.metadata['triforcepool'][player]) outfile.write('Crystals required for GT: %s\n' % (str(self.world.crystals_gt_orig[player]))) @@ -2867,7 +2873,8 @@ 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} +goal_mode = {'ganon': 0, 'pedestal': 1, 'dungeons': 2, 'triforcehunt': 3, 'crystals': 4, 'trinity': 5, + 'ganonhunt': 6, 'completionist': 7} 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 0a50470b..0cee1ab3 100644 --- a/Fill.py +++ b/Fill.py @@ -400,7 +400,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', 'ganonhunt']: gftower_trash_count = random.randint(scaled_trash, max_trash) else: gftower_trash_count = random.randint(0, scaled_trash) diff --git a/ItemList.py b/ItemList.py index c17ce509..1b4b6087 100644 --- a/ItemList.py +++ b/ItemList.py @@ -181,8 +181,12 @@ 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'] - 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']): + if (world.difficulty[player] not in ['normal', 'hard', 'expert'] + or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'trinity', 'crystals', + 'ganonhunt', 'completionist'] + 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') if world.timer in ['ohko', 'timed-ohko']: @@ -344,7 +348,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', 'ganonhunt']: 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' @@ -817,15 +821,15 @@ def add_pot_contents(world, player): world.itempool.append(ItemFactory(item, player)) -def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, bombbag, - door_shuffle, logic, flute_activated): +def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, + bombbag, door_shuffle, logic, flute_activated): pool = [] placed_items = {} precollected_items = [] clock_mode = None - if goal in ['triforcehunt', 'trinity']: + if goal in ['triforcehunt', 'trinity', 'ganonhunt']: if treasure_hunt_total == 0: - treasure_hunt_total = 30 if goal == 'triforcehunt' else 10 + treasure_hunt_total = 30 if goal in ['triforcehunt', 'ganonhunt'] else 10 # triforce pieces max out triforcepool = ['Triforce Piece'] * min(treasure_hunt_total, max_goal) @@ -928,7 +932,7 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt elif timer == 'timed-ohko': pool.extend(diff.timedohko) clock_mode = 'countdown-ohko' - if goal in ['triforcehunt', 'trinity']: + if goal in ['triforcehunt', 'trinity', 'ganonhunt']: pool.extend(triforcepool) for extra in diff.extras: @@ -987,7 +991,7 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer customitemarray["triforce"] = total_items_to_place # Triforce Pieces - if goal in ['triforcehunt', 'trinity']: + if goal in ['triforcehunt', 'trinity', 'ganonhunt']: g, t = set_default_triforce(goal, customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"]) customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"] = g, t @@ -1025,8 +1029,8 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer 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']) - and (customitemarray["triforce"] == 0)): + if ((customitemarray["triforcepieces"] < treasure_hunt_count) + and (goal in ['triforcehunt', 'trinity', 'ganonhunt']) and (customitemarray["triforce"] == 0)): extrapieces = treasure_hunt_count - customitemarray["triforcepieces"] pool.extend(['Triforce Piece'] * extrapieces) itemtotal = itemtotal + extrapieces @@ -1214,7 +1218,7 @@ def get_player_dungeon_item_pool(world, player): # location pool doesn't support larger values at this time def set_default_triforce(goal, custom_goal, custom_total): triforce_goal, triforce_total = 0, 0 - if goal == 'triforcehunt': + if goal in ['triforcehunt', 'ganonhunt']: triforce_goal, triforce_total = 20, 30 elif goal == 'trinity': triforce_goal, triforce_total = 8, 10 diff --git a/Main.py b/Main.py index 907e7a97..48ba4c81 100644 --- a/Main.py +++ b/Main.py @@ -34,7 +34,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -__version__ = '1.2.0.0-u' +__version__ = '1.2.0.1-u' from source.classes.BabelFish import BabelFish diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1905b64f..c69d8094 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -53,7 +53,16 @@ This is similar to insanity mode in ER where door entrances and exits are not pa ## Customizer -Please see [Customizer documentation](docs/Customizer.md) on how to create custom seeds. +Please see [Customizer documentation](docs/Customizer.md) on how to create custom seeds. + +## New Goals + +### Triforce Hunt + Ganon +Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI + +### Completionist +All dungeons not enough for you? You have to obtain every item in the game too. This option turns on the collection rate counter and forces accessibility to be 100% locations. Finish by defeating Ganon. + ## Standard Generation Change @@ -100,10 +109,10 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes -None yet +* 1.2.0.1-u + * Fixed the issue when defeating Agahnim and standing in the doorway can cause door state to linger. # Known Issues -* Standing in the doorway when defeating Aga 1 and being teleported to the Dark World will not clear door state. It may cause issues requiring a Save & Quit to fix. * Decoupled doors can lead to situations where you aren't logically supposed to go back through a door without a big key or small key, but you can if you press the correct direction back through the door first. There are some transitions where you may get stuck without a bomb. These problems are planned to be fixed. -* Logic getting to Skull X room may be wrong if a trap door, big key door, or bombable wall. A bomb jump to get to those pot may be required if you don't have boots to bonk across. \ No newline at end of file +* Logic getting to Skull X room may be wrong if a trap door, big key door, or bombable wall is shuffled there. A bomb jump to get to those pot may be required if you don't have boots to bonk across. \ No newline at end of file diff --git a/Rom.py b/Rom.py index f8b5b107..1f1dfed8 100644 --- a/Rom.py +++ b/Rom.py @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'b6fcbc0d61faffa178135545f18fadbd' +RANDOMIZERBASEHASH = 'fb7f9a0d501ba9ecd0a31066f9a0a973' class JsonRom(object): @@ -730,7 +730,8 @@ 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] not in ['vanilla', 'basic']: dr_flags |= DROptions.Map_Info - if world.collection_rate[player] and world.goal[player] not in ['triforcehunt', 'trinity']: + if ((world.collection_rate[player] or world.goal[player] == 'completionist') + and world.goal[player] not in ['triforcehunt', 'trinity', 'ganonhunt']): dr_flags |= DROptions.Debug if world.doorShuffle[player] not in ['vanilla', 'basic'] and world.logic[player] != 'nologic'\ and world.mixed_travel[player] == 'prevent': @@ -1275,7 +1276,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', '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) @@ -1340,6 +1341,10 @@ 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 ['ganonhunt']: + rom.write_byte(0x18003E, 0x05) # make ganon invincible until all triforce pieces collected + elif world.goal[player] in ['completionist']: + rom.write_byte(0x18003E, 0x09) # make ganon invincible until everything is collected else: rom.write_byte(0x18003E, 0x03) # make ganon invincible until all crystals and aga 2 are collected @@ -2362,6 +2367,10 @@ def write_strings(rom, world, player, team): trinity_crystal_text = ('%d crystal to beat Ganon.' if world.crystals_needed_for_ganon[player] == 1 else '%d crystals to beat Ganon.') % world.crystals_needed_for_ganon[player] tt['sign_ganon'] = 'Three ways to victory! %s Get to it!' % trinity_crystal_text 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] == '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!' diff --git a/Rules.py b/Rules.py index 67417aac..3f4e6b4d 100644 --- a/Rules.py +++ b/Rules.py @@ -60,6 +60,10 @@ def set_rules(world, player): add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player)) elif world.goal[player] in ['triforcehunt', 'trinity']: 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])) + 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] == 'completionist': + add_rule(world.get_location('Ganon', player), lambda state: state.everything(player)) if world.mode[player] != 'inverted': set_big_bomb_rules(world, player) diff --git a/data/base2current.bps b/data/base2current.bps index fed6120e263f9a2a335fbb4bb826d44f5dcd6d98..a3983a652d56acb5442bb49a4732d16ddd2c9109 100644 GIT binary patch delta 6122 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 bMS|K1<@2VtOr8pEOqObw*{@Y68K(R{F(Ryn delta 6061 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