From 277fdff0e1ed39f7cc4da4dede7748687e3f7810 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 8 Sep 2025 12:55:57 -0500 Subject: [PATCH 01/14] Added new Snitch Lady text when Links House is placed there --- Rom.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Rom.py b/Rom.py index b70e983b..9bf71867 100644 --- a/Rom.py +++ b/Rom.py @@ -2103,6 +2103,14 @@ def write_strings(rom, world, player, team): " ~~~2020~~~\n Linlinlin\n\n" " ~~~2019~~~\n Kohrek\n" ) + if not world.is_bombshop_start(player): + links_house = 'Links House' + else: + links_house = 'Big Bomb Shop' + links_house = world.get_region(links_house, player) + links_house = next(e for e in links_house.entrances if e.name != 'Links House S&Q') + if 'Snitch Lady' in links_house.name: + tt['kakariko_alert_guards'] = CompressedTextMapper.convert("Hey @! I'm taking your house!\nk.thx.bye") # Let's keep this guy's text accurate to the shuffle setting. if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple', 'lite', 'lean']: From 4a82b2ad50377989411b77b30e5116a7957d642a Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 21 Sep 2025 13:37:07 -0500 Subject: [PATCH 02/14] Remove unused argument from MultiServer call --- MultiServer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MultiServer.py b/MultiServer.py index c92f9989..44559953 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -83,7 +83,7 @@ def notify_client(client : Client, text : str): logging.info("Notice (Player %s in team %d): %s" % (client.name, client.team+1, text)) asyncio.create_task(send_msgs(client.socket, [['Print', text]])) -async def server(websocket, path, ctx : Context): +async def server(websocket, ctx : Context): client = Client(websocket) ctx.clients.append(client) From cce6abc4685dc25504348444449641e9b98d3f88 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 21 Sep 2025 13:39:07 -0500 Subject: [PATCH 03/14] Fixed issue with Duck overwriting GT cutscene gfx --- Rom.py | 2 +- data/base2current.bps | Bin 136651 -> 136653 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 9bf71867..021f96e6 100644 --- a/Rom.py +++ b/Rom.py @@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '65fae75651987228878051028da066ad' +RANDOMIZERBASEHASH = 'ba0de478a4377673b8c842ac5d12dd16' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index d28bca6d34028f9c2ea3977ae252f75564d57ef4..b76633a917d8f654f915096acc050cba7d4b3eff 100644 GIT binary patch delta 6283 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 From 237ed4bdfd60682ce24c3efe277c406467ed36c3 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 21 Sep 2025 13:56:41 -0500 Subject: [PATCH 04/14] Fixed pogdor (frogdor at PoD entrance) with Kiki following while exiting at PoD --- Rom.py | 2 +- data/base2current.bps | Bin 136653 -> 136674 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 021f96e6..c90871fc 100644 --- a/Rom.py +++ b/Rom.py @@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'ba0de478a4377673b8c842ac5d12dd16' +RANDOMIZERBASEHASH = '1831c9d47b9614b3dac3a68499dcb273' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index b76633a917d8f654f915096acc050cba7d4b3eff..3acec013f0ee2dc9162b62b1be84d582eb130355 100644 GIT binary patch delta 289 zcmV++0p9-2s|ez&2(T&x1akq5{Rgfvk-p(z8$`=M4dtvp6ymPYBeFB7^CmuXM9bfox1jiEF|zs|!`) zfiR8)fZ1!MP4E-R0`Lv3djrr2t@r}a1E~RPsizE8;*}x?<)E+ylMRQPag~wx2=}}fdmF5 n9oU;27`HT>0cisQYYUg3p8+)i9Jke<0q}JQGmb1YNM%K55r%r6 delta 268 zcmV+n0rURis|d}j2(T&x1oH_D_OmVnb{zqmv!x(y0}2J|pv-}W*p(t&lN=-<6o%@c zoSR-qftZ1XoR>Rgfvk-pzq3#z=M4davp6ymPXq9ydb3i2Y)m-CYo$%_6UhSb4Xt|v z&=F+yFRIz=;?(9src!W2F{lSZzzCz zpOd1_>Iz#afDkImJCp*CvzE`ZNEu^Ti;Y5nu<(*o8Gwm^@CTMfih*mQjh_LxVi*B+ zaRpj@fR{11+=&6X4FL?7mX`r40$p&o!j}Po1O|Q_*qe11w@I7 Date: Wed, 15 Oct 2025 18:31:15 -0500 Subject: [PATCH 05/14] New fix for pot drops when at sprite limit --- Rom.py | 2 +- data/base2current.bps | Bin 136674 -> 136766 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index c90871fc..5fe5f3b5 100644 --- a/Rom.py +++ b/Rom.py @@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '1831c9d47b9614b3dac3a68499dcb273' +RANDOMIZERBASEHASH = '5616ec09bb20de9a3d483ea2ca27bc4c' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index 3acec013f0ee2dc9162b62b1be84d582eb130355..a7677e525820cb5824e3850b9857af84385c7669 100644 GIT binary patch delta 703 zcmWlRYe-XJ9LD|cc-&!g*z!__x^=+(;M6087=|voFmEJ6eUg>KY9)Ayv@o&vb<{R2#_J&HZe9ke^cS3ORI?hLCVjo{*Hi8sxu=is{1LtJtbT z*xH1#TW!MD;WgG5;1R=K4f2=83TxM??og?9y~48UtW?S&i~4ToD?ygk8JoqSE_RSy zFEL{-im?rQ-r??zM{K!x9<;JGLB7!JX**>W_-s|#p%b@yERDi2(}__$!uRKK*XU4d zYLD=_WibWDg~47c1$*{v+ii(7B#hc#Ga~KHE)DDnUe}cbF^l!7_p@WZg1CL^pJoDV z+Ram|Feg+_KcU2z-5GBcBoIPFCL{zFXn2zf;{mG*ez%B|Ku13qAb$|*#}sQ!B!}o2 zOGvg&uXAZ=#~0&`D11>;m-xP~JQ=$pv`-t)P$aM77f}*OKW(ia7?AfB7ogc~xp7~< zZr#q!t$I?LBu5tA7DuWxygh!)uF>e>l+o!W&UmtyT>8 zL?{0lQN;-Ox23K>Qo99z7&%XoDJLXL=fXUSeZE3AoKOodNv;dhp|@nh1qCW&t+hWI6OoNQa90|< zIGW&^T^CMGI6U-bzWMZ_7D2nD6N~NN*u*3^H@0`|_ zliA6=`@l_FgB??B9%3JeiiMOJ?xLHzj1|>IO_DBzqIvc3CoPt47ovKv9eZG`YE6l7A;-$OpOM_EZ{xUO6 zs&hDThdC!Fq)5yyHcInOBZO?o{Q*sEPqP=ofgfQjdp#VeGr2WLd|k#gXXTMb^ME)< z8xg1A(1R5;{>DVmihCxd&Fv3T(`ZWljN<=a#$9l&TeJW6$k|f z?yOKNJK)Gt5rB!Gd+`Pg;_cu(nS4>b^ypv{ixd>WX6PdYJ2{{a2@L!iN+u!K40J+O z5PJP-Z|PXsqJfMR;wIYsB_%;^Rc3H|g2WR`6XLA?&upbk&?;yWH=dfo$1&vRUgLT# zJGya5NSC&LkM?%;u|NNE?30xMD_}nchUih^%_Pa3u4()Y!wDF`*$!Z#57#?DP4so( zs}874&W8@DAW9TC?11_jtzzy`d~yF4*W!bPgxt9+=gsJouYvITj%L|r7ZdUlr6gq@ z7Z&dcvNc0Az)9Ce@i8ZKz%aHrAwTtLluHkp-ft8LkMXh-stXNKPO~nv(T=+BGD(1= zBBN0*%`a=VQ4v#=BO?|pcR?+4rQJFcm&8*17T3j2530f${T`HO*DCBN!>#GZH(bz9 zFztBT1zMuC0UvThd1l)gyZ1=z7;5rrTF2~nRa5ENV=av#og2Q6&?l$LtDCyd-TV*4 CBKRZ# From 917d126023207d8d0f7a1a0498821981f6c5d8b3 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 18 Oct 2025 02:27:43 -0500 Subject: [PATCH 06/14] Fixed Ganon silvers hint for free-standing items --- Rom.py | 2 +- data/base2current.bps | Bin 136766 -> 136743 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 5fe5f3b5..e29406ae 100644 --- a/Rom.py +++ b/Rom.py @@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '5616ec09bb20de9a3d483ea2ca27bc4c' +RANDOMIZERBASEHASH = 'e20f407ef55da945f893d32ee6fc541d' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index a7677e525820cb5824e3850b9857af84385c7669..cb0632acee3e06141779ffe3ab48ecf6326f8f5b 100644 GIT binary patch delta 11963 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 k*S*u@%@B4(AK!AWT2_p;;LRJnT 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 From 048487fce0b53a4346c83c7b3a0f84c46dbec3de Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 19 Oct 2025 23:35:17 -0500 Subject: [PATCH 07/14] Moved random settings resolutions into helper function --- Main.py | 51 ++++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/Main.py b/Main.py index df672380..34a819e7 100644 --- a/Main.py +++ b/Main.py @@ -97,29 +97,7 @@ def main(args, seed=None, fish=None): if args.securerandom: world.seed = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(9)) - world.crystals_needed_for_ganon = {player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1)} - world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1)} - world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} - - world.treasure_hunt_count = {} - world.treasure_hunt_total = {} - for p in args.triforce_goal: - if int(args.triforce_goal[p]) != 0 or int(args.triforce_pool[p]) != 0 or int(args.triforce_goal_min[p]) != 0 or int(args.triforce_goal_max[p]) != 0 or int(args.triforce_pool_min[p]) != 0 or int(args.triforce_pool_max[p]) != 0: - if int(args.triforce_goal[p]) != 0: - world.treasure_hunt_count[p] = int(args.triforce_goal[p]) - elif int(args.triforce_goal_min[p]) != 0 and int(args.triforce_goal_max[p]) != 0: - world.treasure_hunt_count[p] = random.randint(int(args.triforce_goal_min[p]), int(args.triforce_goal_max[p])) - else: - world.treasure_hunt_count[p] = 8 if world.goal[p] == 'trinity' else 20 - if int(args.triforce_pool[p]) != 0: - world.treasure_hunt_total[p] = int(args.triforce_pool[p]) - elif int(args.triforce_pool_min[p]) != 0 and int(args.triforce_pool_max[p]) != 0: - world.treasure_hunt_total[p] = random.randint(max(int(args.triforce_pool_min[p]), world.treasure_hunt_count[p] + int(args.triforce_min_difference[p])), min(int(args.triforce_pool_max[p]), world.treasure_hunt_count[p] + int(args.triforce_max_difference[p]))) - else: - world.treasure_hunt_total[p] = 10 if world.goal[p] == 'trinity' else 30 - else: - # this will be handled in ItemList.py and custom item pool is used to determine the numbers - world.treasure_hunt_count[p], world.treasure_hunt_total[p] = 0, 0 + resolve_random_settings(world, args) world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} world.finish_init() @@ -519,6 +497,33 @@ def init_world(args, fish): return world +def resolve_random_settings(world, args): + world.crystals_needed_for_ganon = {player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1)} + world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1)} + world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} + + world.treasure_hunt_count = {} + world.treasure_hunt_total = {} + for p in args.triforce_goal: + if int(args.triforce_goal[p]) != 0 or int(args.triforce_pool[p]) != 0 or int(args.triforce_goal_min[p]) != 0 or int(args.triforce_goal_max[p]) != 0 or int(args.triforce_pool_min[p]) != 0 or int(args.triforce_pool_max[p]) != 0: + if int(args.triforce_goal[p]) != 0: + world.treasure_hunt_count[p] = int(args.triforce_goal[p]) + elif int(args.triforce_goal_min[p]) != 0 and int(args.triforce_goal_max[p]) != 0: + world.treasure_hunt_count[p] = random.randint(int(args.triforce_goal_min[p]), int(args.triforce_goal_max[p])) + else: + world.treasure_hunt_count[p] = 8 if world.goal[p] == 'trinity' else 20 + if int(args.triforce_pool[p]) != 0: + world.treasure_hunt_total[p] = int(args.triforce_pool[p]) + elif int(args.triforce_pool_min[p]) != 0 and int(args.triforce_pool_max[p]) != 0: + world.treasure_hunt_total[p] = random.randint(max(int(args.triforce_pool_min[p]), world.treasure_hunt_count[p] + int(args.triforce_min_difference[p])), min(int(args.triforce_pool_max[p]), world.treasure_hunt_count[p] + int(args.triforce_max_difference[p]))) + else: + world.treasure_hunt_total[p] = 10 if world.goal[p] == 'trinity' else 30 + else: + # this will be handled in ItemList.py and custom item pool is used to determine the numbers + world.treasure_hunt_count[p], world.treasure_hunt_total[p] = 0, 0 + return + + def set_starting_inventory(world, args): for player in range(1, world.players + 1): if args.usestartinventory[player]: From ba444f4bbc5abf745891d65f5b678866c01b596a Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 27 Oct 2025 23:23:10 -0500 Subject: [PATCH 08/14] Remove duplicated hint possibility between location and item hinting --- Rom.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/Rom.py b/Rom.py index e29406ae..55ddde86 100644 --- a/Rom.py +++ b/Rom.py @@ -2229,6 +2229,7 @@ def write_strings(rom, world, player, team): # Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable. locations_to_hint = InconvenientLocations.copy() + hinted_locations = [] if world.doorShuffle[player] == 'vanilla': locations_to_hint.extend(InconvenientDungeonLocations) if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: @@ -2246,7 +2247,6 @@ def write_strings(rom, world, player, team): second_item = hint_text(world.get_location('Swamp Palace - West Chest', player).item) first_item = hint_text(world.get_location('Swamp Palace - Big Key Chest', player).item) this_hint = f'The westmost chests in Swamp Palace contain {first_item} and {second_item}.' - tt[hint_locations.pop(0)] = this_hint elif location == 'Mire Left': if random.randint(0, 1) == 0: first_item = hint_text(world.get_location('Misery Mire - Compass Chest', player).item) @@ -2255,38 +2255,31 @@ def write_strings(rom, world, player, team): second_item = hint_text(world.get_location('Misery Mire - Compass Chest', player).item) first_item = hint_text(world.get_location('Misery Mire - Big Key Chest', player).item) this_hint = f'The westmost chests in Misery Mire contain {first_item} and {second_item}.' - tt[hint_locations.pop(0)] = this_hint elif location == 'Tower of Hera - Big Key Chest': item = hint_text(world.get_location(location, player).item) this_hint = f'Waiting in the Tower of Hera basement leads to {item}.' - tt[hint_locations.pop(0)] = this_hint elif location == 'Ganons Tower - Big Chest': item = hint_text(world.get_location(location, player).item) this_hint = f'The big chest in Ganon\'s Tower contains {item}.' - tt[hint_locations.pop(0)] = this_hint elif location == 'Thieves\' Town - Big Chest': item = hint_text(world.get_location(location, player).item) this_hint = f'The big chest in Thieves\' Town contains {item}.' - tt[hint_locations.pop(0)] = this_hint elif location == 'Ice Palace - Big Chest': item = hint_text(world.get_location(location, player).item) this_hint = f'The big chest in Ice Palace contains {item}.' - tt[hint_locations.pop(0)] = this_hint elif location == 'Eastern Palace - Big Key Chest': item = hint_text(world.get_location(location, player).item) this_hint = f'The antifairy guarded chest in Eastern Palace contains {item}.' - tt[hint_locations.pop(0)] = this_hint elif location == 'Sahasrahla': item = hint_text(world.get_location(location, player).item) this_hint = f'Sahasrahla seeks a green pendant for {item}.' - tt[hint_locations.pop(0)] = this_hint elif location == 'Graveyard Cave': item = hint_text(world.get_location(location, player).item) this_hint = f'The cave north of the graveyard contains {item}.' - tt[hint_locations.pop(0)] = this_hint else: this_hint = f'{location} contains {hint_text(world.get_location(location, player).item)}.' - tt[hint_locations.pop(0)] = this_hint + hinted_locations.append(location) + tt[hint_locations.pop(0)] = this_hint # Lastly we write hints to show where certain interesting items are. # It is done the way it is to re-use the silver code and also to give one hint per each type of item regardless @@ -2299,9 +2292,10 @@ def write_strings(rom, world, player, team): if world.owShuffle[player] != 'vanilla' or world.owMixed[player]: # Adding a guaranteed hint for the Flute in overworld shuffle. this_location = world.find_items_not_key_only(flute_item, player) - if this_location: + if this_location and this_location not in hinted_locations: this_hint = this_location[0].item.hint_text + ' can be found ' + hint_text(this_location[0]) + '.' this_hint = this_hint[0].upper() + this_hint[1:] + hinted_locations.append(this_location) tt[hint_locations.pop(0)] = this_hint items_to_hint.remove(flute_item) if world.keyshuffle[player] not in ['none', 'nearby', 'universal']: @@ -2317,11 +2311,12 @@ def write_strings(rom, world, player, team): while hint_count > 0 and len(items_to_hint) > 0: this_item = items_to_hint.pop(0) this_location = world.find_items_not_key_only(this_item, player) - if this_location: + if this_location and this_location not in hinted_locations: random.shuffle(this_location) item_name = this_location[0].item.hint_text item_name = item_name[0].upper() + item_name[1:] this_hint = f'{item_name} can be found {hint_text(this_location[0])}.' + hinted_locations.append(this_location) tt[hint_locations.pop(0)] = this_hint hint_count -= 1 From fdbe9cf9fd8930398ea75eec33944465743a6ea6 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 29 Oct 2025 00:20:41 -0500 Subject: [PATCH 09/14] Implemented Custom Goal Framework --- BaseClasses.py | 44 ++++++-- ItemList.py | 41 +++++-- Main.py | 153 +++++++++++++++++++++++++- Rom.py | 152 +++++++++++++++++++------- Rules.py | 180 +++++++++++++++++++++++++++---- Text.py | 1 + data/base2current.bps | Bin 136743 -> 137030 bytes docs/Customizer.md | 152 +++++++++++++++++++++----- source/classes/CustomSettings.py | 6 +- 9 files changed, 629 insertions(+), 100 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 1a230d6b..fc4f6394 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -187,6 +187,7 @@ class World(object): set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False}) set_player_attr('prizes', {'dig;': [], 'pull': [0, 0, 0], 'crab': [0, 0], 'stun': 0, 'fish': 0, 'enemies': []}) set_player_attr('default_zelda_region', 'Hyrule Dungeon Cellblock') + set_player_attr('custom_goals', {'gtentry': None, 'ganongoal': None, 'pedgoal': None, 'murahgoal': None}) set_player_attr('exp_cache', defaultdict(dict)) set_player_attr('enabled_entrances', {}) @@ -1211,16 +1212,35 @@ class CollectionState(object): def item_count(self, item, player): return self.prog_items[item, player] - def everything(self, player): - all_locations = self.world.get_filled_locations(player) - all_locations.remove(self.world.get_location('Ganon', player)) - return (len([x for x in self.locations_checked if x.player == player]) + def everything(self, player, all_except=0): + all_locations = [x for x in self.world.get_filled_locations(player) if not x.locked] + return (len([x for x in self.locations_checked if x.player == player and not x.locked]) + all_except >= len(all_locations)) 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 + def has_pendants(self, count, player): + pendants = ['Green Pendant', 'Red Pendant', 'Blue Pendant'] + return len([pendant for pendant in pendants if self.has(pendant, player)]) >= count + + def has_bosses(self, count, player, prize_type=None): + dungeons = 'Eastern Palace', 'Desert Palace', 'Tower of Hera', 'Palace of Darkness', 'Swamp Palace', "Thieves' Town", 'Skull Woods', 'Ice Palace', 'Misery Mire', 'Turtle Rock' + reachable_bosses = 0 + for d in dungeons: + region = self.world.get_region(f'{d} - Boss Kill', player) + if prize_type is None or prize_type in region.dungeon.prize.name: + if self.can_reach(region, None, player): + reachable_bosses += 1 + return reachable_bosses >= count + + def has_crystal_bosses(self, count, player): + return self.has_bosses(count, player, 'Crystal') + + def has_pendant_bosses(self, count, player): + return self.has_bosses(count, player, 'Pendant') + def can_lift_rocks(self, player): return self.has('Power Glove', player) or self.has('Titans Mitts', player) @@ -3015,6 +3035,7 @@ class Spoiler(object): 'flute_mode': self.world.flute_mode, 'bow_mode': self.world.bow_mode, 'goal': self.world.goal, + 'custom_goals': self.world.custom_goals, 'ow_shuffle': self.world.owShuffle, 'ow_terrain': self.world.owTerrain, 'ow_crossed': self.world.owCrossed, @@ -3250,8 +3271,19 @@ class Spoiler(object): if self.metadata['goal'][player] in ['triforcehunt', 'trinity', 'ganonhunt']: 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])) - outfile.write('Crystals Required for Ganon:'.ljust(line_width) + '%s\n' % str(self.world.crystals_ganon_orig[player])) + custom = self.metadata['custom_goals'][player] + if 'requirements' in custom['gtentry']: + outfile.write('GT Entry Requirement:'.ljust(line_width) + 'custom\n') + else: + outfile.write('GT Entry Requirement:'.ljust(line_width) + '%s crystals\n' % str(self.world.crystals_gt_orig[player])) + if 'requirements' in custom['ganongoal']: + outfile.write('Ganon Requirement:'.ljust(line_width) + 'custom\n') + else: + outfile.write('Ganon Requirement:'.ljust(line_width) + '%s crystals\n' % str(self.world.crystals_ganon_orig[player])) + if 'requirements' in custom['pedgoal']: + outfile.write('Pedestal Requirement:'.ljust(line_width) + 'custom\n') + if 'requirements' in custom['murahgoal']: + outfile.write('Murahdahla Requirement:'.ljust(line_width) + 'custom\n') outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player]) outfile.write('\n') outfile.write('Accessibility:'.ljust(line_width) + '%s\n' % self.metadata['accessibility'][player]) diff --git a/ItemList.py b/ItemList.py index 65d67f57..b7dd5e18 100644 --- a/ItemList.py +++ b/ItemList.py @@ -228,12 +228,17 @@ def generate_itempool(world, player): if world.timer in ['ohko', 'timed-ohko']: world.can_take_damage = False - if world.goal[player] in ['pedestal', 'triforcehunt']: + goal_req = None + if world.custom_goals[player]['ganongoal'] and 'requirements' in world.custom_goals[player]['ganongoal']: + goal_req = world.custom_goals[player]['ganongoal']['requirements'][0] + if world.goal[player] in ['pedestal', 'triforcehunt'] or (goal_req and goal_req['condition'] == 0x00): set_event_item(world, player, 'Ganon', 'Nothing') else: set_event_item(world, player, 'Ganon', 'Triforce') - if world.goal[player] in ['triforcehunt', 'trinity']: + if world.custom_goals[player]['murahgoal'] and 'requirements' in world.custom_goals[player]['murahgoal']: + goal_req = world.custom_goals[player]['murahgoal']['requirements'][0] + if world.goal[player] in ['triforcehunt', 'trinity'] or (goal_req and goal_req['condition'] != 0x00): region = world.get_region('Hyrule Castle Courtyard', player) loc = Location(player, "Murahdahla", parent=region) region.locations.append(loc) @@ -1159,11 +1164,17 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt 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 not in ['pedestal', 'trinity']: - place_item('Master Sword Pedestal', swords_to_use.pop()) - else: - place_item('Master Sword Pedestal', 'Triforce') + if world.custom_goals[player]['pedgoal'] and 'requirements' in world.custom_goals[player]['pedgoal'] and world.custom_goals[player]['pedgoal']['requirements'][0]['condition'] == 0x00: + place_item('Master Sword Pedestal', 'Nothing') + world.get_location('Master Sword Pedestal', player).locked = True pool.append(swords_to_use.pop()) + else: + if goal not in ['pedestal', 'trinity']: + place_item('Master Sword Pedestal', swords_to_use.pop()) + else: + place_item('Master Sword Pedestal', 'Triforce') + world.get_location('Master Sword Pedestal', player).locked = True + pool.append(swords_to_use.pop()) else: pool.extend(diff.progressivesword if want_progressives() else diff.basicsword) if swords == 'assured': @@ -1189,8 +1200,12 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt # note: massage item pool now handles shrinking the pool appropriately - if goal in ['pedestal', 'trinity'] and swords != 'vanilla': + if world.custom_goals[player]['pedgoal'] and 'requirements' in world.custom_goals[player]['pedgoal'] and world.custom_goals[player]['pedgoal']['requirements'][0]['condition'] == 0x00: + place_item('Master Sword Pedestal', 'Nothing') + world.get_location('Master Sword Pedestal', player).locked = True + elif goal in ['pedestal', 'trinity'] and swords != 'vanilla': place_item('Master Sword Pedestal', 'Triforce') + world.get_location('Master Sword Pedestal', player).locked = True if world.bow_mode[player].startswith('retro'): pool = [item.replace('Single Arrow', 'Rupees (5)') for item in pool] pool = [item.replace('Arrows (10)', 'Rupees (5)') for item in pool] @@ -1352,8 +1367,12 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer elif timer == 'ohko': clock_mode = 'ohko' - if goal in ['pedestal', 'trinity']: + if world.custom_goals[player]['pedgoal'] and 'requirements' in world.custom_goals[player]['pedgoal'] and world.custom_goals[player]['pedgoal']['requirements'][0]['condition'] == 0x00: + place_item('Master Sword Pedestal', 'Nothing') + world.get_location('Master Sword Pedestal', player).locked = True + elif goal in ['pedestal', 'trinity']: place_item('Master Sword Pedestal', 'Triforce') + world.get_location('Master Sword Pedestal', player).locked = True if mode == 'standard': if world.keyshuffle[player] == 'universal': @@ -1466,8 +1485,12 @@ def make_customizer_pool(world, player): elif timer == 'ohko': clock_mode = 'ohko' - if world.goal[player] in ['pedestal', 'trinity']: + if world.custom_goals[player]['pedgoal'] and 'requirements' in world.custom_goals[player]['pedgoal'] and world.custom_goals[player]['pedgoal']['requirements'][0]['condition'] == 0x00: + place_item('Master Sword Pedestal', 'Nothing') + world.get_location('Master Sword Pedestal', player).locked = True + elif world.goal[player] in ['pedestal', 'trinity']: place_item('Master Sword Pedestal', 'Triforce') + world.get_location('Master Sword Pedestal', player).locked = True guaranteed_items = alwaysitems + ['Magic Mirror', 'Moon Pearl'] if world.is_tile_swapped(0x18, player) or world.flute_mode[player] == 'active': diff --git a/Main.py b/Main.py index 34a819e7..41a46b11 100644 --- a/Main.py +++ b/Main.py @@ -521,8 +521,159 @@ def resolve_random_settings(world, args): else: # this will be handled in ItemList.py and custom item pool is used to determine the numbers world.treasure_hunt_count[p], world.treasure_hunt_total[p] = 0, 0 + + if world.customizer: + def process_goal(goal_type): + goal_input = goals[player][goal_type] + world.custom_goals[player][goal_type] = goal = {} + if 'cutscene_gfx' in goal_input and goal_type in ['gtentry', 'pedgoal', 'murahgoal']: + gfx = goal_input['cutscene_gfx'] + if type(gfx) is str: + from Tables import item_gfx_table + if gfx.lower() == 'random': + gfx = random.choice(list(item_gfx_table.keys())) + if gfx in item_gfx_table: + goal['cutscene_gfx'] = (item_gfx_table[gfx][1] + (0x8000 if not item_gfx_table[gfx][0] else 0), item_gfx_table[gfx][2]) + else: + raise Exception(f'Invalid name "{gfx}" in customized {goal_type} cutscene gfx') + else: + goal['cutscene_gfx'] = gfx + if 'requirements' in goal_input: + if goal_type == 'ganongoal' and world.goal[player] == 'pedestal': + goal['requirements'] = [0x00] + goal['logic'] = False + return + goal['requirements'] = [] + goal['logic'] = {} + if 'goaltext' in goal_input: + goal['goaltext'] = goal_input['goaltext'] + else: + raise Exception(f'Missing goal text for {goal_type}') + + req_table = { + 'Invulnerable': 0x00, + 'Pendants': 0x01, + 'Crystals': 0x02, + 'PendantBosses': 0x03, + 'CrystalBosses': 0x04, + 'Bosses': 0x05, + 'Agahnim1Defeated': 0x06, + 'Agahnim1': 0x06, + 'Aga1': 0x06, + 'Agahnim2Defeated': 0x07, + 'Agahnim2': 0x07, + 'Aga2': 0x07, + 'GoalItemsCollected': 0x08, + 'GoalItems': 0x08, + 'TriforcePieces': 0x08, + 'TriforceHunt': 0x08, + 'MaxCollectionRate': 0x09, + 'CollectionRate': 0x09, + 'Collection': 0x09, + 'CustomGoal': 0x0A, + 'Custom': 0x0A, + } + if isinstance(goal_input['requirements'], list): + for r in list(goal_input['requirements']): + req = {} + try: + req['condition'] = req_table[list(r.keys())[0]] + if req['condition'] == req_table['Invulnerable']: + goal['requirements']= [req] + goal['logic'] = False + break + elif req['condition'] == req_table['CustomGoal']: + if isinstance(r['address'], int) and 0x7E0000 <= r['address'] <= 0x7FFFFF: + compare_table = { + 'minimum': 0x00, + 'at least': 0x00, + 'equal': 0x01, + 'equals': 0x01, + 'equal to': 0x01, + 'any flag': 0x02, + 'all flags': 0x03, + 'flags match': 0x03, + 'count bits': 0x04, + 'count flags': 0x04, + } + if r['comparison'] in compare_table: + options = compare_table[r['comparison']] + if r['address'] >= 0x7F0000: + options |= 0x10 + if isinstance(r['target'], int) and 0 <= r['target'] <= 0xFFFF: + if 'size' in r and r['size'] in ['word', '16-bit', '16bit', '16 bit', '16', '2-byte', '2byte', '2 byte', '2-bytes', '2 bytes']: + options |= 0x08 + req['target'] = r['target'] + elif 0 <= r['target'] <= 0xFF: + req['target'] = r['target'] + else: + raise Exception(f'Invalid custom goal target for {goal_type}, must be an 8-bit integer') + req.update({'address': r['address'] & 0xFFFF, 'options': options}) + goal['requirements'].append(req) + else: + raise Exception(f'Invalid custom goal target for {goal_type}, must be a 16-bit integer') + else: + raise KeyError(f'Invalid custom goal comparison for {goal_type}') + else: + raise Exception(f'Custom goal address for {goal_type} only allows 0x7Exxxx and 0x7Fxxxx addresses') + else: + if req['condition'] not in [req_table['Aga1'], req_table['Aga2']]: + if 'target' not in r: + req['condition'] |= 0x80 + else: + if isinstance(r['target'], int): + if req['condition'] < req_table['TriforcePieces']: + if 0 <= r['target'] <= 0xFF: + req['target'] = r['target'] + else: + raise Exception(f'Invalid {list(r.keys())[0]} requirement target for {goal_type}, must be an 8-bit integer') + else: + if 0 <= r['target'] <= 0xFFFF: + req['target'] = r['target'] + else: + raise Exception(f'Invalid {list(r.keys())[0]} requirement target for {goal_type}, must be a 16-bit integer') + elif isinstance(r['target'], str): + if r['target'].lower() == 'random': + req['target'] = 'random' + elif r['target'].endswith('%') and 1 <= int(r['target'][:-1]) <= 100: + req['target'] = req['target'] + else: + raise Exception(f'Invalid {list(r.keys())[0]} requirement target for {goal_type}') + if req['condition'] & 0x7F == req_table['Pendants']: + goal['logic']['pendants'] = req['target'] or 3 + elif req['condition'] & 0x7F == req_table['Crystals']: + goal['logic']['crystals'] = req['target'] or 7 + elif req['condition'] & 0x7F == req_table['PendantBosses']: + goal['logic']['pendant_bosses'] = req['target'] or 3 + elif req['condition'] & 0x7F == req_table['CrystalBosses']: + goal['logic']['crystal_bosses'] = req['target'] or 7 + elif req['condition'] & 0x7F == req_table['Bosses']: + goal['logic']['bosses'] = req['target'] or 10 + elif req['condition'] & 0x7F == req_table['Aga1']: + goal['logic']['aga1'] = True + elif req['condition'] & 0x7F == req_table['Aga2']: + goal['logic']['aga2'] = True + elif req['condition'] & 0x7F == req_table['TriforcePieces']: + goal['logic']['goal_items'] = req['target'] or None + elif req['condition'] & 0x7F == req_table['CollectionRate']: + goal['logic']['collection'] = req['target'] or None + goal['requirements'].append(req) + except KeyError: + raise KeyError(f'Invalid {goal_type} requirement: {r}') + else: + raise KeyError(f'Invalid {goal_type} requirement definition') + if 'logic' in goal_input and goal['logic'] is not None: + goal['logic'].update(goal_input['logic']) + return + + goals = world.customizer.get_goals() + for player in range(1, world.players + 1): + if goals and player in goals: + for g in ['gtentry', 'ganongoal', 'pedgoal', 'murahgoal']: + if g in goals[player]: + process_goal(g) return - + def set_starting_inventory(world, args): for player in range(1, world.players + 1): diff --git a/Rom.py b/Rom.py index 55ddde86..9a43f813 100644 --- a/Rom.py +++ b/Rom.py @@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'e20f407ef55da945f893d32ee6fc541d' +RANDOMIZERBASEHASH = 'b7817fb00fb0a918a7fa275ff8f4c3be' class JsonRom(object): @@ -1218,8 +1218,6 @@ def patch_rom(world, rom, player, team, is_mystery=False): rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest if world.is_pyramid_open(player): rom.initial_sram.pre_open_pyramid_hole() - if world.crystals_needed_for_gt[player] == 0: - rom.initial_sram.pre_open_ganons_tower() rom.write_byte(0x18008F, 0x01 if world.is_atgt_swapped(player) else 0x00) # AT/GT swapped rom.write_byte(0xF5D73, 0xF0) # bees are catchable rom.write_byte(0xF5F10, 0xF0) # bees are catchable @@ -1247,22 +1245,105 @@ def patch_rom(world, rom, player, team, is_mystery=False): (0x02 if 'bombs' in world.escape_assist[player] else 0x00) | (0x04 if 'magic' in world.escape_assist[player] else 0x00))) # Escape assist - if world.goal[player] in ['pedestal', 'triforcehunt']: - rom.write_byte(0x1801A8, 0x01) # make ganon invincible - elif world.goal[player] in ['dungeons']: - rom.write_byte(0x1801A8, 0x02) # make ganon invincible until all dungeons are beat - elif world.goal[player] in ['crystals', 'trinity']: - rom.write_byte(0x1801A8, 0x04) # make ganon invincible until all crystals - elif world.goal[player] in ['ganonhunt']: - rom.write_byte(0x1801A8, 0x05) # make ganon invincible until all triforce pieces collected - elif world.goal[player] in ['completionist']: - rom.write_byte(0x1801A8, 0x0B) # make ganon invincible until everything is collected - else: - rom.write_byte(0x1801A8, 0x03) # make ganon invincible until all crystals and aga 2 are collected + gt_entry, ped_pull, ganon_goal, murah_goal = [], [], [], [] + # 00: Invulnerable + # 01: All pendants + # 02: All crystals + # 03: Pendant bosses + # 04: Crystal bosses + # 05: Prize bosses + # 06: Agahnim 1 defeated + # 07: Agahnim 2 defeated + # 08: Goal items collected (ie. Triforce Pieces) + # 09: Max collection rate + # 0A: Custom goal - rom.write_byte(0x18019A, world.crystals_needed_for_gt[player]) - rom.write_byte(0x1801A6, world.crystals_needed_for_ganon[player]) - rom.write_byte(0x1801A2, 0x00) # ped requirement is vanilla, set to 0x1 for special requirements + def get_goal_bytes(type): + goal_bytes = [] + for req in world.custom_goals[player][type]['requirements']: + goal_bytes += [req['condition']] + if req['condition'] == 0x0A: + # custom goal + goal_bytes += [req['options']] + goal_bytes += int16_as_bytes(req['address']) + if 0x08 & req['options'] == 0: + goal_bytes += [req['target']] + else: + goal_bytes += int16_as_bytes(req['target']) + elif 'target' in req: + if req['condition'] & 0x7F < 0x08: + goal_bytes += [req['target']] + else: + goal_bytes += int16_as_bytes(req['target']) + return goal_bytes + + if world.custom_goals[player]['gtentry'] and 'requirements' in world.custom_goals[player]['gtentry']: + gt_entry += get_goal_bytes('gtentry') + else: + gt_entry += [0x02, world.crystals_needed_for_gt[player]] + if len(gt_entry) == 0 or gt_entry == [0x02, 0x00]: + rom.initial_sram.pre_open_ganons_tower() + + if world.custom_goals[player]['pedgoal'] and 'requirements' in world.custom_goals[player]['pedgoal']: + ped_pull += get_goal_bytes('pedgoal') + else: + ped_pull += [0x81] + + if world.custom_goals[player]['murahgoal'] and 'requirements' in world.custom_goals[player]['murahgoal']: + murah_goal += get_goal_bytes('murahgoal') + else: + if world.goal[player] in ['triforcehunt', 'trinity']: + murah_goal += [0x88] + else: + murah_goal += [0x00] + + if world.custom_goals[player]['ganongoal'] and 'requirements' in world.custom_goals[player]['ganongoal']: + ganon_goal += get_goal_bytes('ganongoal') + else: + if world.goal[player] in ['pedestal', 'triforcehunt']: + ganon_goal = [0x00] + elif world.goal[player] in ['dungeons']: + ganon_goal += [0x81, 0x82, 0x06, 0x07] # pendants, crystals, and agas + elif world.goal[player] in ['crystals', 'trinity']: + 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 ['completionist']: + ganon_goal += [0x81, 0x82, 0x06, 0x07, 0x89] # AD and max collection rate + else: + ganon_goal += [0x02, world.crystals_needed_for_ganon[player], 0x07] # crystals and aga2 + + gt_entry += [0xFF] + ped_pull += [0xFF] + ganon_goal += [0xFF] + murah_goal += [0xFF] + start_address = 0x8198 + 8 + + write_int16(rom, 0x180198, start_address) + rom.write_bytes(snes_to_pc(0xB00000 + start_address), gt_entry) + start_address += len(gt_entry) + + write_int16(rom, 0x18019A, start_address) + rom.write_bytes(snes_to_pc(0xB00000 + start_address), ganon_goal) + start_address += len(ganon_goal) + + write_int16(rom, 0x18019C, start_address) + rom.write_bytes(snes_to_pc(0xB00000 + start_address), ped_pull) + start_address += len(ped_pull) + + write_int16(rom, 0x18019E, start_address) + rom.write_bytes(snes_to_pc(0xB00000 + start_address), murah_goal) + start_address += len(murah_goal) + + if start_address > 0x81D8: + raise Exception("Custom Goal data too long to fit in allocated space, try reducing the amount of requirements.") + + # gt entry + gtentry = world.custom_goals[player]['gtentry'] + if gtentry and 'cutscene_gfx' in gtentry: + gfx = gtentry['cutscene_gfx'] + write_int16(rom, snes_to_pc(0x3081D8), gfx[0]) + rom.write_byte(snes_to_pc(0x3081E6), gfx[1]) # block HC upstairs doors in rain state in standard mode prevent_rain = world.mode[player] == 'standard' and world.shuffle[player] != 'vanilla' and world.logic[player] != 'nologic' @@ -1654,26 +1735,6 @@ def patch_rom(world, rom, player, team, is_mystery=False): write_enemizer_tweaks(rom, world, player) write_strings(rom, world, player, team) - # gt entry - if world.customizer: - gtentry = world.customizer.get_gtentry() - if gtentry and player in gtentry: - gtentry = gtentry[player] - if 'cutscene_gfx' in gtentry: - gfx = gtentry['cutscene_gfx'] - if type(gfx) is str: - from Tables import item_gfx_table - if gfx.lower() == 'random': - gfx = random.choice(list(item_gfx_table.keys())) - if gfx in item_gfx_table: - write_int16(rom, snes_to_pc(0x3081AA), item_gfx_table[gfx][1] + (0x8000 if not item_gfx_table[gfx][0] else 0)) - rom.write_byte(snes_to_pc(0x3081AC), item_gfx_table[gfx][2]) - else: - logging.getLogger('').warning('Invalid name "%s" in customized GT entry cutscene gfx', gfx) - else: - write_int16(rom, snes_to_pc(0x3081AA), gfx[0]) - rom.write_byte(snes_to_pc(0x3081AC), gfx[1]) - # write initial sram rom.write_initial_sram() @@ -2502,6 +2563,21 @@ 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!' + + def get_custom_goal_text(type): + goal_text = world.custom_goals[player][type]['goaltext'] + if '%d' in goal_text: + return goal_text % world.custom_goals[player][type]['requirements'][0]['target'] + return goal_text + + if world.custom_goals[player]['gtentry'] and 'goaltext' in world.custom_goals[player]['gtentry']: + tt['sign_ganons_tower'] = get_custom_goal_text('gtentry') + if world.custom_goals[player]['ganongoal'] and 'goaltext' in world.custom_goals[player]['ganongoal']: + tt['sign_ganon'] = get_custom_goal_text('ganongoal') + if world.custom_goals[player]['pedgoal'] and 'goaltext' in world.custom_goals[player]['pedgoal']: + tt['mastersword_pedestal_goal'] = get_custom_goal_text('pedgoal') + if world.custom_goals[player]['murahgoal'] and 'goaltext' in world.custom_goals[player]['murahgoal']: + tt['murahdahla'] = get_custom_goal_text('murahgoal') tt['kakariko_tavern_fisherman'] = TavernMan_texts[random.randint(0, len(TavernMan_texts) - 1)] diff --git a/Rules.py b/Rules.py index d87eaa45..5368b2f5 100644 --- a/Rules.py +++ b/Rules.py @@ -61,25 +61,33 @@ def set_rules(world, player): drop_rules(world, player) challenge_room_rules(world, player) - if world.goal[player] == 'dungeons': - # require all dungeons to beat ganon - add_rule(world.get_location('Ganon', player), lambda state: state.has_beaten_aga(player) and state.has('Beat Agahnim 2', player) and state.has('Beat Boss', player, 10)) - elif world.goal[player] in ['crystals', 'ganon']: - add_rule(world.get_location('Ganon', player), lambda state: state.has_crystals(world.crystals_needed_for_ganon[player], player)) - if 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] in ['triforcehunt', 'trinity']: - if world.goal[player] == 'trinity': + if world.custom_goals[player]['ganongoal'] and 'requirements' in world.custom_goals[player]['ganongoal']: + rule = get_goal_rule('ganongoal', world, player) + add_rule(world.get_location('Ganon', player), rule) + else: + if world.goal[player] == 'dungeons': + # require all dungeons to beat ganon + add_rule(world.get_location('Ganon', player), lambda state: state.has_beaten_aga(player) and state.has('Beat Agahnim 2', player) and state.has('Beat Boss', player, 10)) + elif world.goal[player] in ['crystals', 'ganon']: add_rule(world.get_location('Ganon', player), lambda state: state.has_crystals(world.crystals_needed_for_ganon[player], player)) - for location in world.get_region('Hyrule Castle Courtyard', player).locations: - if location.name == 'Murahdahla': - add_rule(location, 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.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] in ['triforcehunt', 'trinity']: + if world.goal[player] == 'trinity': + 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] == 'completionist': + add_rule(world.get_location('Ganon', player), lambda state: state.everything(player)) + for location in world.get_region('Hyrule Castle Courtyard', player).locations: + if location.name == 'Murahdahla': + if world.custom_goals[player]['murahgoal'] and 'requirements' in world.custom_goals[player]['murahgoal']: + rule = get_goal_rule('murahgoal', world, player) + add_rule(location, rule) + else: + add_rule(location, lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player])) if (world.flute_mode[player] != 'active' and not world.is_tile_swapped(0x18, player) and 'Ocarina (Activated)' not in list(map(str, [i for i in world.precollected_items if i.player == player]))): @@ -175,6 +183,132 @@ def add_rule(spot, rule, combine='and'): else: spot.access_rule = lambda state: rule(state) and old_rule(state) + +def get_goal_rule(goal_type, world, player): + goal_data = world.custom_goals[player][goal_type] + if goal_data['requirements'][0]['condition'] == 0x00: + return lambda state: False + rule = None + def add_to_rule(new_rule): + nonlocal rule + if rule is None: + rule = new_rule + else: + rule = and_rule(rule, new_rule) + if 'logic' in goal_data: + for logic, data in goal_data['logic'].items(): + if logic == 'pendants': + pendants = int(data) + add_to_rule(lambda state: state.has_pendants(pendants, player)) + elif logic == 'crystals': + crystals = int(data) + add_to_rule(lambda state: state.has_crystals(crystals, player)) + elif logic == 'pendant_bosses': + pendant_bosses = int(data) + add_to_rule(lambda state: state.has_pendant_bosses(pendant_bosses, player)) + elif logic == 'crystal_bosses': + crystal_bosses = int(data) + add_to_rule(lambda state: state.has_crystal_bosses(crystal_bosses, player)) + elif logic == 'bosses': + bosses = int(data) + add_to_rule(lambda state: state.has('Beat Boss', player, bosses)) + elif logic == 'aga1': + add_to_rule(lambda state: state.has('Beat Agahnim 1', player)) + elif logic == 'aga2': + add_to_rule(lambda state: state.has('Beat Agahnim 2', player)) + elif logic == 'goal_items': + if data is not None: + goal_items = int(data) + add_to_rule(lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= goal_items) + else: + add_to_rule(lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player])) + elif logic == 'collection': + if data is not None: + all_locations = [x for x in world.get_filled_locations(player) if not x.locked] + collection = int(data) - len(all_locations) + add_to_rule(lambda state: state.everything(player, collection)) + else: + add_to_rule(lambda state: state.everything(player)) + elif logic == 'item': + for item in data: + item_name = item + if '(' in item_name: + item_name, region_name = item_name.rsplit(' (', 1) + region_name = region_name.rstrip(')') + region = world.get_region(region_name, player) + if region and region.dungeon: + region_name = region.dungeon.name + else: + try: + if world.get_dungeon(region_name, player): + pass + except: + raise Exception(f'Invalid dungeon/region name in custom goal logic for item {item}') + item_name = f'{item_name} ({region_name})' + if '=' in item_name: + item_name, count = item_name.rsplit('=', 1) + count = int(count) + add_to_rule(lambda state: state.has(item_name, player, count)) + else: + add_to_rule(lambda state: state.has(item_name, player)) + elif logic == 'access': + for region_name in data: + region = world.get_region(region_name, player) + if not region: + raise Exception(f'Invalid region name in custom goal logic for region: {region_name}') + add_to_rule(lambda state: state.can_reach(region, None, player)) + elif logic == 'ability': + for ability in data: + param = None + if '(' in ability: + ability, param = ability.split('(', 1) + param = param.rstrip(')') + if ability == 'FarmBombs': + add_to_rule(lambda state: state.can_farm_bombs(player)) + elif ability == 'FarmRupees': + add_to_rule(lambda state: state.can_farm_rupees(player)) + elif ability == 'NoBunny': + if not param: + raise Exception(f'NoBunny ability requires a region argument in custom goal logic') + bunny_region = param + region = world.get_region(bunny_region, player) + if region: + add_to_rule(lambda state: state.is_not_bunny(bunny_region, player)) + else: + raise Exception(f'Invalid region name in custom goal logic for NoBunny ability: {param}') + elif ability == 'CanUseBombs': + add_to_rule(lambda state: state.can_use_bombs(player)) + elif ability == 'CanBonkDrop': + add_to_rule(lambda state: state.can_collect_bonkdrops(player)) + elif ability == 'CanLift': + add_to_rule(lambda state: state.can_lift_rocks(player)) + elif ability == 'MagicExtension': + magic_count = 16 + if param: + magic_count = int(param) + add_to_rule(lambda state: state.can_extend_magic(player, magic_count)) + elif ability == 'CanStun': + add_to_rule(lambda state: state.can_stun_enemies(player)) + elif ability == 'CanKill': + if param: + enemy_count = int(param) + add_to_rule(lambda state: state.can_kill_most_things(player, enemy_count)) + else: + add_to_rule(lambda state: state.can_kill_most_things(player)) + elif ability == 'CanShootArrows': + add_to_rule(lambda state: state.can_shoot_arrows(player)) + elif ability == 'CanFlute': + add_to_rule(lambda state: state.can_flute(player)) + elif ability == 'HasFire': + add_to_rule(lambda state: state.has_fire_source(player)) + elif ability == 'CanMelt': + add_to_rule(lambda state: state.can_melt_things(player)) + elif ability == 'HasMMMedallion': + add_to_rule(lambda state: state.has_misery_mire_medallion(player)) + elif ability == 'HasTRMedallion': + add_to_rule(lambda state: state.has_turtle_rock_medallion(player)) + return rule if rule is not None else lambda state: True + def add_bunny_rule(spot, player): if spot.can_cause_bunny(player): add_rule(spot, lambda state: state.has_Pearl(player)) @@ -244,7 +378,11 @@ def global_rules(world, player): set_rule(world.get_entrance('Flute Spot 8', player), lambda state: state.can_flute(player)) # overworld location rules - set_rule(world.get_location('Master Sword Pedestal', player), lambda state: state.has('Red Pendant', player) and state.has('Blue Pendant', player) and state.has('Green Pendant', player)) + if world.custom_goals[player]['pedgoal'] and 'requirements' in world.custom_goals[player]['pedgoal']: + rule = get_goal_rule('pedgoal', world, player) + set_rule(world.get_location('Master Sword Pedestal', player), rule) + else: + set_rule(world.get_location('Master Sword Pedestal', player), lambda state: state.has('Red Pendant', player) and state.has('Blue Pendant', player) and state.has('Green Pendant', player)) set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player)) set_rule(world.get_location('Old Man', player), lambda state: state.has('Return Old Man', player)) set_rule(world.get_location('Old Man Drop Off', player), lambda state: state.has('Escort Old Man', player)) @@ -412,9 +550,13 @@ def global_rules(world, player): set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) # sword required to cast magic (!) set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has('Turtle Opened', player)) + if world.custom_goals[player]['gtentry'] and 'requirements' in world.custom_goals[player]['gtentry']: + rule = get_goal_rule('gtentry', world, player) + set_rule(world.get_entrance('Ganons Tower' if not world.is_atgt_swapped(player) else 'Agahnims Tower', player), rule) + else: + set_rule(world.get_entrance('Ganons Tower' if not world.is_atgt_swapped(player) else 'Agahnims Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) if not world.is_atgt_swapped(player): set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_beam_sword(player)) - set_rule(world.get_entrance('Ganons Tower' if not world.is_atgt_swapped(player) else 'Agahnims Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) # Start of door rando rules # TODO: Do these need to flag off when door rando is off? - some of them, yes diff --git a/Text.py b/Text.py index 1a66b22f..07fb2e4d 100644 --- a/Text.py +++ b/Text.py @@ -2043,5 +2043,6 @@ class TextTable(object): text['ganon_phase_3_no_silvers'] = CompressedTextMapper.convert("You can't best me without silver arrows!") text['ganon_phase_3_silvers'] = CompressedTextMapper.convert("Oh no! Silver! My one true weakness!") text['murahdahla'] = CompressedTextMapper.convert("Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n{PAUSE3}\n… … …\nWait! You can see me? I knew I should have\nhidden in a hollow tree.") + text['mastersword_pedestal_goal'] = CompressedTextMapper.convert("To claim thy reward, you must present all 3 Pendants of Virtue.") text['end_pad_data'] = bytearray([0xfb]) text['terminator'] = bytearray([0xFF, 0xFF]) diff --git a/data/base2current.bps b/data/base2current.bps index cb0632acee3e06141779ffe3ab48ecf6326f8f5b..f3da2d969e97766c7199198fc76924a7582f94c6 100644 GIT binary patch delta 12309 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 delta 12042 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%0DwMuGscL}* zx>^8+kWoJ9p8{Y8KF0SD;42De^x+`b0Jfl{LNF9K8V?rYTw%O0C%1$VD)Z%>)3w?) zPN=2`)Z)#Yh(IsN+k<8m1NXoi_O|>ukzf(@~_Fs>Trh_M_a=CZVXMfX2DhA zSFPV<1dG91;E4Vy#$F$13kog)X5W6U`I-i^Ww1RVs6#%1J$hcV@k}kg8f`sOt6n|1 zvuf~iG`1A`z~chY#!?U;bRCDv?qVZ1ZBK?kmzuk{UT0_Is|W4R)aYt)>O5^(e$M84>e8oUw=iO5#my!tT}u0q$! zfEzi<0(F&vOBTaC?x{o35eGO_Yju%02 zJ9$&M3oSQrm#txhk@zayxBm8xYuqD;c{i?EzHhU7*DYwdX!E|!cE+s@@ytvnh*EwT z6DE~c514JU`4IMyVtu{%Q3#|0Cxay@w!?xdWEsA6O0DJ1wLmgp7(z2 zeT_dit0IDt!waz0cH!hFg}uFZdtooK1IGLpU@4zBjdSWt{50docOa-gZ*;#iaI&w_ zhf5A2MaL#tGMiMfE?J6u&+iep2J{c5H5s-Bf*E0QEDak_gSPU>2oE(s0=C;21l-j2 zs79{q+WgwnBBp-NH{_KdMHhLbpOa%`YeP_SuAU&4h;P$$?8l`e(H9>6hJhnch$Xp# z6R{=BsI(*(lAbf{=ytJ8rxEAtGzKeXgc(T0pIYP>7A9Y6_B3(@)`: Link is required to not be a bunny in the specified region +* `MagicExtension()`: Link has a magic meter with a higher capacity (parameter is optional, a value of 8 represents one normal full magic bar, default value if left blank is 16, which is equivalent to half magic or one bottle with access to a green potion) +* `CanStun`: Link has the ability to stun enemies +* `CanKill()`: Link has the ability to kill most enemy types (parameter is optional, higher number tends to favor weapons that don't consume ammo, default value is 6 enemies) +* `CanShootArrows`: Link has ability to fire arrows at enemies +* `CanBonkDrop`: Link is able to retrieve Bonk Drops from trees and rocks +* `CanLift`: Link is able to lift basic rocks +* `CanFlute`: Link has ability to use flute (includes access to flute activation) +* `HasFire`: Link has a fire source +* `CanMelt`: Link can melt ice with Firerod or Bombos +* `HasMMMedallion`: Link has the medallion required for unlocking Misery Mire entrance +* `HasTRMedallion`: Link has the medallion required for unlocking Turtle Rock entrance + +#### (example) + +This entire section is very advanced and can be used to make very powerful customizations to the game. To make the overall definition more clear, we provide an example that makes use of a lot of the controls in place: Ganon requiring both 5 crystals AND requiring opening the GT Big Chest + +```yaml +goals: + 1: + ganongoal: + goaltext: You’ll need %d crystals and to open the Big Chest in Ganon’s Tower + requirements: + - Crystals: + target: 5 + - Custom: + address: 0x7ef118 + target: 0x80 + comparison: flags match + logic: + item: + - Big Key (GT Big Chest) + access: + - GT Big Chest + ability: + - NoBunny(GT Big Chest) +``` + ### placements This must be defined by player. Each player number should be listed with the appropriate placement list. @@ -348,27 +476,3 @@ prices: Dark Death Mountain Shop - Right: 300 Dark Lake Hylia Shop - Left: 200 ``` - -### gt_entry - -This must be defined by player. This is where you are able to customize aspects of GT entry - -#### cutscene_gfx - -This is where you can define custom GFX to be used in the GT entry cutscene. For convenience, there are a number of pre-defined names that can be used to indicate already known GFX values built into the ROM. There are too many to list, but a full list can be found in `item_gfx_table` in `Tables.py`. You can also use `Random` and it will take a random one from the aforementioned table. - -``` -gt_entry: - 1: - cutscene_gfx: Mirror Shield -``` - -Alternatively, you may also supply a custom address and palette ID, respectively, if you are injecting your own personal custom GFX into the ROM. - -``` -gt_entry: - 1: - cutscene_gfx: - - 0x8140 - - 0x04 -``` diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 0b421fca..80b23e0d 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -299,9 +299,9 @@ class CustomSettings(object): return self.file_source['enemies'] return None - def get_gtentry(self): - if 'gt_entry' in self.file_source: - return self.file_source['gt_entry'] + def get_goals(self): + if 'goals' in self.file_source: + return self.file_source['goals'] return None From 6cb05f18ca6917e97aa11c1210c90581f9d6cc30 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 29 Oct 2025 14:07:21 -0500 Subject: [PATCH 10/14] Changed vital/useful hints to logic hints --- Rom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rom.py b/Rom.py index 9a43f813..8b5f595c 100644 --- a/Rom.py +++ b/Rom.py @@ -2394,13 +2394,13 @@ def write_strings(rom, world, player, team): for name, district in world.districts[player].items(): hint_type = 'foolish' choices = [] - item_count, item_type = 0, 'useful' + item_count, item_type = 0, 'logic' for loc_name in district.locations: location_item = world.get_location(loc_name, player).item if location_item.advancement: if 'Heart Container' in location_item.name or location_item.compass or location_item.map: continue - itm_type = 'useful' if useful_item_for_hint(location_item, world) else 'vital' + itm_type = 'logic' hint_type = 'path' if item_type == itm_type: choices.append(location_item) From cf488fda36f724fdd36b921ebcb53f7ad2380ea1 Mon Sep 17 00:00:00 2001 From: hiimcody1 Date: Mon, 20 Oct 2025 17:53:34 -0400 Subject: [PATCH 11/14] Initial custom header implementation --- Main.py | 2 +- Plando.py | 2 +- Rom.py | 21 ++++++++++++++++++--- resources/app/cli/args.json | 5 +++-- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Main.py b/Main.py index 41a46b11..6324f3bc 100644 --- a/Main.py +++ b/Main.py @@ -290,7 +290,7 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): rom = JsonRom() if args.jsonout else LocalRom(args.rom) - patch_rom(world, rom, player, team, bool(args.mystery)) + patch_rom(world, rom, player, team, bool(args.mystery), str(args.rom_header) if args.rom_header else None) if args.race: patch_race_rom(rom) diff --git a/Plando.py b/Plando.py index cc97562f..2c948435 100755 --- a/Plando.py +++ b/Plando.py @@ -74,7 +74,7 @@ def main(args): logger.info('Patching ROM.') rom = LocalRom(args.rom) - patch_rom(world, rom, 1, 1, False) + patch_rom(world, rom, 1, 1, False, str(args.rom_header) if args.rom_header else None) apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, args.sprite, args.ow_palettes, args.uw_palettes) diff --git a/Rom.py b/Rom.py index 8b5f595c..68855318 100644 --- a/Rom.py +++ b/Rom.py @@ -418,7 +418,7 @@ def handle_native_dungeon(location, itemid): return itemid -def patch_rom(world, rom, player, team, is_mystery=False): +def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None): random.seed(world.rom_seeds[player]) # progressive bow silver arrow hint hack @@ -1744,8 +1744,23 @@ def patch_rom(world, rom, player, team, is_mystery=False): # 21 bytes from Main import __version__ from OverworldShuffle import __version__ as ORVersion - seedstring = f'{world.seed:09}' if isinstance(world.seed, int) else world.seed - rom.name = bytearray(f'OR{__version__.split("-")[0].replace(".","")[0:3]}_{team+1}_{player}_{seedstring}\0', 'utf8')[:21] + if rom_header: + if len(rom_header) > 21: + raise Exception('ROM header too long. Max 21 bytes, found %d bytes.' % len(rom_header)) + elif '|' in rom_header: + gen, seedstring = rom_header.split('|', 1) + gen = f'{gen:<3}' + seedstring = f'{int(seedstring):09}' if seedstring.isdigit() else seedstring[:9] + rom.name = bytearray(f'{gen}_{team+1}_{player}_{seedstring}\0', 'utf8')[:21] + elif len(rom_header) <= 9: + seedstring = f'{int(rom_header):09}' if rom_header.isdigit() else rom_header + rom.name = bytearray(f'OR{__version__.split("-")[0].replace(".","")[0:3]}_{team+1}_{player}_{seedstring}\0', 'utf8')[:21] + else: + rom.name = bytearray(rom_header, 'utf8')[:21] + else: + seedstring = f'{world.seed:09}' if isinstance(world.seed, int) else world.seed + rom.name = bytearray(f'OR{__version__.split("-")[0].replace(".","")[0:3]}_{team+1}_{player}_{seedstring}\0', 'utf8')[:21] + rom.name.extend([0] * (21 - len(rom.name))) rom.write_bytes(0x7FC0, rom.name) diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index d042d93b..b0dbf825 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -641,5 +641,6 @@ }, "outputname": {}, "notes": {}, - "code": {} -} \ No newline at end of file + "code": {}, + "rom_header" : {} +} From fd28ef77d2f7c406e0b3be5ea94e1932ef701121 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 29 Oct 2025 15:01:56 -0500 Subject: [PATCH 12/14] Fixed issue with Locksmith despawning after purple chest turn-in when a follower is stored --- Rom.py | 2 +- data/base2current.bps | Bin 137030 -> 137030 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 68855318..6579220b 100644 --- a/Rom.py +++ b/Rom.py @@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'b7817fb00fb0a918a7fa275ff8f4c3be' +RANDOMIZERBASEHASH = 'ec0ac9063daaeb39faf1282faa3fdba8' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index f3da2d969e97766c7199198fc76924a7582f94c6..2f28105755a45943fc16a8d9725fba2536f5e5f0 100644 GIT binary patch delta 70 zcmV-M0J;Cht_a4i2(T&x1I$7*vn>Nz%ORs^cI2%v4$u*=%!07V4DbezM1YbzM1byZ cM3W0DfO>?3r_Q&h&H$Y)+>jNB{r; delta 70 zcmV-M0J;Cht_a4i2(T&x1J6P*vn>Nz%ORg=cI1z1k%`F+@RI?CkqIL0 Date: Wed, 29 Oct 2025 15:17:24 -0500 Subject: [PATCH 13/14] Implemented Custom Goal Framework --- BaseClasses.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index fc4f6394..f2305238 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -3272,17 +3272,17 @@ class Spoiler(object): 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]) custom = self.metadata['custom_goals'][player] - if 'requirements' in custom['gtentry']: + if custom['gtentry'] and 'requirements' in custom['gtentry']: outfile.write('GT Entry Requirement:'.ljust(line_width) + 'custom\n') else: outfile.write('GT Entry Requirement:'.ljust(line_width) + '%s crystals\n' % str(self.world.crystals_gt_orig[player])) - if 'requirements' in custom['ganongoal']: + if custom['ganongoal'] and 'requirements' in custom['ganongoal']: outfile.write('Ganon Requirement:'.ljust(line_width) + 'custom\n') else: outfile.write('Ganon Requirement:'.ljust(line_width) + '%s crystals\n' % str(self.world.crystals_ganon_orig[player])) - if 'requirements' in custom['pedgoal']: + if custom['pedgoal'] and 'requirements' in custom['pedgoal']: outfile.write('Pedestal Requirement:'.ljust(line_width) + 'custom\n') - if 'requirements' in custom['murahgoal']: + if custom['murahgoal'] and 'requirements' in custom['murahgoal']: outfile.write('Murahdahla Requirement:'.ljust(line_width) + 'custom\n') outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player]) outfile.write('\n') From a5ce59c7e82ef84fc0b29c569b1a1554af3d6b54 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 29 Oct 2025 15:41:56 -0500 Subject: [PATCH 14/14] Version bump 0.6.1.0 --- CHANGELOG.md | 13 +++++++++++++ OverworldShuffle.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e83b3b2..ad540537 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 0.6.1.0 +- New Custom Goal framework (see Customizer.md) +- New 'logic' hint terminology to replace vital/useful terminology +- Removed possibility of duplicated hints +- New fix for hammering pot drops when at sprite limit +- Fixed issue with incorrect Ganon silvers hint +- Fixed issue with Locksmith despawning after purple chest if a follower is stored +- Fixed pogdor glitch (frogdor but at PoD entrance and Kiki following) +- Fixed issue with Duck gfx overwriting GT Cutscene gfx +- New CLI argument to allow external generators to supply a custom ROM Header +- Fixed error with extra argument in MultiServer call +- Added new text if Link's House is placed at any Snitch Lady house + ## 0.6.0.8 - Re-fixed issue with Old Man spawning on pyramid - Allowing Zelda to be in TT Prison for follower shuffle escape diff --git a/OverworldShuffle.py b/OverworldShuffle.py index bfe555cd..80e781d0 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.6.0.8' +version_number = '0.6.1.0' # branch indicator is intentionally different across branches version_branch = '-u'